Codeforces #279 Div.2 – C. Hacking Cypher

Codeforces #279 (Div.2)のCが結構いい問題だと思ったので備忘録的に解説します。

問題

最大100万桁ある数が与えられる。この数をどこかの桁で左右に2つに分割した時に、前側の数がaで、後側の数がbで割り切れるようにしたい。ただし、分割した後の数は先頭に0を含んではいけない。もし、そのような分割があれば、その答えを、なければ-1を出力せよ。


解法(間違い)

ものすごく単純に考えると、前後の数を多倍長でもって、その都度剰余を計算すればよさそうです。

とっても頭が弱い私は「あぁ、多倍長演算めんどいなぁ。Rubyでやろw」といって、以下のコードをSubmitします。

s = STDIN.gets.strip
t = STDIN.gets.split(" ");
a = t[0].to_i
b = t[1].to_i

m = s.length
dig = 1
for i in 1..m-1
  dig = dig * 10
end

ok = false

aa = 0
bb = s.to_i
while dig != 1
  tmp = bb / dig
  bb -= tmp * dig
  aa = aa * 10 + tmp
  dig = dig / 10

  if bb / dig == 0
    next
  end

  if aa % a == 0 and bb % b == 0
    puts("YES")
    puts(aa)
    puts(bb)
    ok = true
    break
  end
end

if not ok
  puts("NO")
end

まぁ、普通に小さい入力だったら問題なく動作するわけですが、1時間ほど経って、何者かにHackされてしまう…

よくよく考えると、剰余を求める計算の計算量を厳密に捉えれば上のコードの計算量はO(N^2)のオーダーになるためTLEすることに気づきます。


解法 (本物)

じゃあどうするかというと、余りだけが分かればいいので、あらかじめ与えられた数と10の累乗のbでの剰余を計算しておいて、逐一、前側の数のaでの剰余と後側の数のbでの剰余を更新していってあげればよいことが分かります。

言葉で言ってもわかりづらいので、以下のコードを見てください。最後の文字出力のところが分かりづらいかもですが、コピーしていると時間の無駄なので、こっちの方が早いです(無駄な努力)。


#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <cstring>
#include <climits>
#include <algorithm>
#include <functional>
#include <numeric>
#include <map>
#include <set>
#include <stack>
#include <vector>
#include <queue>
#include <bitset>
#include <string>
using namespace std;

#define REP(i,n) for(int i=0; i<n; i++)
#define REPP(i,s,e) for(int i=s; i<=e; i++)
#define PB(a) push_back(a)
#define MP(i,s) make_pair(i,s)
#define SZ(a) (int)(a).size()
#define ALL(a) (a).begin(), (a).end()
#define PRT(a) cerr << #a << " = " << (a) << endl
#define PRT2(a,b) cerr << #a << " = " << (a) << ", " << #b << " = " << (b) << endl
#define PRT3(a,b,c) cerr << (a) << ", " << (b) << ", " << (c) << endl

typedef long long lint;
typedef pair<lint,lint> pll;

string s;
lint a, b;

lint dig[1000011];

void solve() {
    lint aa = 0;
    lint bb = 0;
    dig[0] = 1;
    int n = s.size();
    REP(i,n) {
        bb = (bb * 10 + (s[i] - '0')) % b;
        dig[i+1] = (dig[i] * 10) % b;
    }

    REP(i,n-1) {
        aa = (aa * 10 + (s[i] - '0')) % a;
        bb = (bb - ((s[i] - '0') * dig[n-i-1]) % b + b)  % b; 
        if(s[i+1] == '0') continue;
        if(aa == 0 && bb == 0) {
            printf("YES\n");
            int c = s[i+1];
            s[i+1] = '\0';
            printf("%s\n", s.c_str());
            s[i+1] = c;
            printf("%s\n", s.c_str()+(i+1));
            return;
        }
    }
    printf("NO\n");
}

void coding() {
    while(cin>>s) {
        cin>>a>>b;
        solve();
    }
}

// #define _LOCAL_TEST

int main() {
#ifdef _LOCAL_TEST
    clock_t startTime = clock();
    freopen("c.in", "r", stdin);
#endif

    coding();

#ifdef _LOCAL_TEST
    clock_t elapsedTime = clock() - startTime;
    cout << endl;
    cout << (elapsedTime / 1000.0) << " sec elapsed." << endl;
    cout << "This is local test" << endl;
    cout << "Do not forget to comment out _LOCAL_TEST" << endl << endl;
#endif
}