[ARC137] D - Prefix XORs

[Q] https://atcoder.jp/contests/arc137/tasks/arc137_d

メモ化再帰で O(NlogN)。
本番TLEでとても悔しい。処理の高速化は十分だったけど、下処理が2つ足りていなかった。
1. メモをmapでしてしまったのでlogN増え、
2. log2()とかpow()を使ったのでlogN増えた。
N=M=100000なら十分間に合っていた。悔しい…。

・考察
とりあえずN=7 M=9 のすべての遷移を表示し観察。

N=7 M=9
A[]: 1 2 4 8 16 32 64

N:          0       1       2       3       4       5       6
cycle:      1       2       4       4       8       8       8
A[0]: 0000001 0000010 0000100 0001000 0010000 0100000 1000000 
A[1]: 0000001 0000011 0000111 0001111 0011111 0111111 1111111 
A[2]: 0000001 0000010 0000101 0001010 0010101 0101010 1010101 
A[3]: 0000001 0000011 0000110 0001100 0011001 0110011 1100110 
A[4]: 0000001 0000010 0000100 0001000 0010001 0100010 1000100 
A[5]: 0000001 0000011 0000111 0001111 0011110 0111100 1111000 
A[6]: 0000001 0000010 0000101 0001010 0010100 0101000 1010000 
A[7]: 0000001 0000011 0000110 0001100 0011000 0110000 1100000 
A[8]: 0000001 0000010 0000100 0001000 0010000 0100000 1000000 
A[9]: 0000001 0000011 0000111 0001111 0011111 0111111 1111111 

1. 各Nについて、周期があることがわかる。
N=0で1周期 (1)
N=1で2周期 (10->11)
N=2,3で4周期 (100->111->101->110)
N=4,5,6,7で8周期。(10000->11111->10101->11001->10001->11110->...)​
この先も、1, 2, 44, 8888,16*8 ,32*16...の周期になるんだろう。

周期での高速化ができるので、N=6について。
A(6, 0) = 1000000 は
A(6, 8) = 1000000 になる。
A(6, 16) = 1000000 になる。

他にも
A(6,1) = A(6,9) = A(6,17)...になる
A(6,2) = A(6,10) = A(6,18)...になるだろう。

2. 周期以外の法則を見つける
求めたいA(x, y)は、Aから上に2の累乗移動した場所と、Aから左に2の累乗移動した場所との、XORになっている。
A(x, y) = A(x-d, y) ^ A(x, y-d)

N:          0       1       2       3       4       5       6
cycle:      1       2       4       4       8       8       8
A[0]: 0000001 0000010 0000100 0001000 0010000 0100000 1000000 
A[1]: 0000001 0000011 0000111 0001111 0011111 0111111 1111111 
A[2]: 0000001 0000010 0000101 0001010 0010101 0101010 1010101 
A[3]: 0000001 0000011 0000110 0001100 0011001 0110011 1100110 
A[4]: 0000001 0000010 0000100 0001000 0010001 0100010 1000100 
A[5]: 0000001 0000011 0000111 0001111 0011110 0111100 1111000 
A[6]: 0000001 0000010 0000101 0001010 0010100 0101000 1010000 
A[7]: 0000001 0000011 0000110 0001100 0011000 0110000 1100000 
A[8]: 0000001 0000010 0000100 0001000 0010000 0100000 1000000 
A[9]: 0000001 0000011 0000111 0001111 0011111 0111111 1111111 


1. A(6,0) = 1000000
これは入力からすぐ求まる。

2. A(6,1) = 1111111 を求める。
これは A(5,1)^A(6,0)になっている。

3. A(6,2) = 1010101 を求める。
これは A(4,2)^A(6,0) になっている。

4. A(6,3)は、A(4,3)^A(6,1)
5. A(6,4)は、A(2,4)^A(6,0)
6. A(6,5)は、A(1,5)^A(6,1)...

整理すると
1. A(6,0): 入力
2. A(6,1): A(5,1)^A(6,0) = A(6-1,1)^A(6,1-1)
3. A(6,2): A(4,2)^A(6,0) = A(6-2,2)^A(6,2-2)
4. A(6,3): A(4,3)^A(6,1) = A(6-2,3)^A(6,3-2)
5. A(6,4): A(2,4)^A(6,0) = A(6-4,4)^A(6,4-4)
6. A(6,5): A(2,5)^A(6,1) = A(6-4,5)^A(6,5-4)
7. A(6,6): A(2,6)^A(6,2) = A(6-4,6)^A(6,6-4)
8. A(6,7): A(2,7)^A(6,3) = A(6-4,7)^A(6,7-4)

求めたいA()は、Aから上と左に2の累乗移動した場所との、XORになっている。

・A(6,6) は A(6,2)^A(2,6)で求まる

N:          0       1       2       3       4       5       6
cycle:      1       2       4       4       8       8       8
A[0]: 0000001 0000010 0000100 0001000 0010000 0100000 1000000 
A[1]: 0000001 0000011 0000111 0001111 0011111 0111111 1111111 
A[2]: 0000001 0000010 0000101 0001010 0010101 0101010<1010101> 
                                                      ~~~~~~~ A(6,2)
A[3]: 0000001 0000011 0000110 0001100 0011001 0110011 1100110 
A[4]: 0000001 0000010 0000100 0001000 0010001 0100010 1000100 
A[5]: 0000001 0000011 0000111 0001111 0011110 0111100 1111000 
A[6]: 0000001 0000010<0000101>0001010 0010100 0101000<1010000>
                      ~~~~~~~ A(2,6)                  ~~~~~~~ A(6,6)
A[7]: 0000001 0000011 0000110 0001100 0011000 0110000 1100000 
A[8]: 0000001 0000010 0000100 0001000 0010000 0100000 1000000 
A[9]: 0000001 0000011 0000111 0001111 0011111 0111111 1111111 

この2つの法則で高速化すればO(NlogN)。
Q. メモ化はvector?
A. メモをmapでやるとTLE。

Q. 2の累乗をpow()とかlog2()とかで計算?
A. logNの処理になるのでTLE。前計算必須。

実装

ll N, M;
vector<ll> A;
ll B[1000100]; // 前計算1
ll D[1000100]; // 前計算2
ll cnt; // debug
vector<ll> memo[1000100];

void initB() {
 // B
 // 0:1
 // 1:2
 // 2:4
 // 3:4
 // 4:8
 ll nx = 1;
 for (ll i = 1; i <= N; ++i) {
   while (nx < i) nx *= 2;
   B[i - 1] = nx;
 }

 // D
 // 0:0
 // 1:1
 // 2:2
 // 3:2
 // 4:4
 nx = 1;
 for (ll i = 1; i < N; ++i) {
   while (nx * 2 <= i) nx *= 2;
   D[i] = nx;
 }
}

ll dfs(ll x, ll y) {
 ++cnt;
 // 周期の高速化
 y %= B[x];
 if (x == 0) return A[0];
 if (y == 0) return A[x];
 if ((ll)memo[x].size() > y) return memo[x][y];
 ll p = min(x, y);
 ll d = D[p];
 // 上に2^maxしたものと、左に2^maxしたものとのXORをとる
 ll ret = dfs(x - d, y) ^ dfs(x, y - d);
 memo[x].push_back(ret);
 return ret;
}

int main() {
 cincout();

 cin >> N >> M;
 initB();
 A.resize(N);
 rep(i, N) cin >> A[i];
 rep(i, N) { memo[i].push_back(A[i]); }

 rep(m, M) { cout << dfs(N - 1, m + 1) << " \n"[m == M - 1]; }
 // cerr << cnt << endl;
}

https://atcoder.jp/contests/arc137/submissions/30250173


この記事が気に入ったらサポートをしてみませんか?