Google Code Jam Japan 2011 Fianl B: バクテリアの増殖
問題概要
X[0]=A(<1000), X[n+1] = X[n]^X[n]で定義される数列Xがある。X[B](B<=(small ? 2 : 1000))をmod C(<1000)で求める問題。
考えたこと
- 全部mod Cでやるだけじゃダメなの?
- ダメだ。指数の部分はmod Cだと値が変わる。
- ということはX[i] mod Cの値だけ持っておいてもダメなのか。
- 指数の部分もループに入るのでループの長さをLとするとX[i] mod Lの値も覚えておく必要がある。
- でも、ループの長さってそれぞれ違うんだからLというよりL[ X[i] ]か。こんがらがる…。
- とはいえだいぶ見えてきた。後はループの長さが分かれば良い。これは前処理でいけそう?後、ループに入るまでの長さと、ループの先頭の値もついでに計算しとこう。
- それを持っておけば後は何とか書けそう。ループまで届かないときもあるが、それは超序盤に限る(X[i]<1000)ので、そのときは愚直にやろう。
- 前処理はset使った手抜き実装でO(1000^3 * log 1000)。setのlogとはいえ1~2分あれば終わるだろう。
- サンプルが思ってたより弱くて1WAもらったけど無事通った。いろんな値での剰余計算が出てきて混乱した。普段はひたすら%MODとつけるだけなので。
- 冷静に考えるとsmallはちゃっちゃと書いて出しといた方が安全だった。
practiceで通ったコード
計算量は前処理O(MAX_C^3)(ただしこの実装では手抜きのためlogがくっつく)、その後はO(B*C*log C)。
#include <cstdio> #include <vector> #include <set> #include <cstring> using namespace std; const int MAX_C = 1000; const int INF = 1<<29; int A, B, C, NOW; int L[MAX_C+1][MAX_C+1]; int TO[MAX_C+1][MAX_C+1]; int LOOP[MAX_C+1][MAX_C+1]; int X[MAX_C+1]; //現在値 % MOD int Y[MAX_C+1]; //バッファ int pow(int x, int k, int mod){ if(x == 0){ return 0; } int ret = 1, base = x; for(;k;k>>=1){ if(k&1){ ret = (ret * base) % mod; } base = (base * base) % mod; } return ret; } int solve(){ if(C==1){ return 0; } if(C==2){ return A&1; } if(A==1){ return 1; } for(int i=1; i<=C; i++){ X[i] = A % i; } for(int i=0; i<B; i++){ if(NOW < INF){ for(int j=1; j<=C; j++){ Y[j] = pow(X[j], NOW, j); } } else{ for(int j=1; j<=C; j++){ const int tar = TO[X[j]][j]; const int mod = LOOP[tar][j]; const int p = (( X[mod] - L[X[j]][j] - 1) % mod + mod ) % mod; Y[j] = (tar * pow(X[j], p , j)) % j; } } for(int j=1; j<=C; j++){ X[j] = Y[j]; } if(NOW == 2){ NOW = 4; } else if(NOW == 3){ NOW = 27; } else if(NOW == 4){ NOW = 256; } else{ NOW = INF; } } return X[C]; } void pre_solve(){ for(int mod=1; mod<=MAX_C; mod++){ L[0][mod] = 0; TO[0][mod] = 0; LOOP[0][mod] = 1; for(int x=1; x<mod; x++){ set<int> ss; vector<int> xs; int cur = x; for(int i=0; ; i++){ if(!ss.insert(cur).second){ int key = 0; for(key = 0; xs[key] != cur; key++); L[x][mod] = key; TO[x][mod] = cur; LOOP[x][mod] = i - key; break; } else{ xs.push_back(cur); cur = ( cur * x ) % mod; } } } } } void initialize(){ scanf("%d%d%d",&A,&B,&C); NOW = A; } int main(){ int T; scanf("%d",&T); pre_solve(); /* printf("TO[3][5] = %d\n", TO[3][5]); printf("TO[2][8] = %d\n", TO[2][8]); printf("L[2][8] = %d\n", L[2][8]); printf("LOOP[2][8] = %d\n", LOOP[2][8]); */ for(int c=1; c<=T; c++){ initialize(); printf("Case #%d: %d\n", c, solve()); } return 0; }
セットがあまりに重くて前処理に時間がかかりすぎるので、bool配列で高速化。
13a14 > bool visited[MAX_C+1]; 95a97 > memset(visited, false, sizeof(visited)); 97c99 < if(!ss.insert(cur).second){ --- > if(visited[cur]){ 106a109 > visited[cur] = true;