题目链接
HDU7416 - PKSAI1005特别感谢 主机 姐的支持!
题意给定大小为n n n 的数组{ a n } \{a_n\} { a n } .
构造b b b 数组,b x , y = { a y , x = 0 b x − 1 , y ⊕ b x − 1 , y + 1 , x > 0 b_{x, y} = \begin{cases} a_y, & x=0 \\ b_{x-1,y} \oplus b_{x-1,y+1} , & x>0 \end{cases}~ b x , y = { a y , b x − 1 , y ⊕ b x − 1 , y + 1 , x = 0 x > 0 (其中,x + y ⩽ n − 1 x+y \leqslant n-1 x + y ⩽ n − 1 ,⊕ \oplus ⊕ 表示异或操作)
有q q q 次询问,给定( x , y ) (x, y) ( x , y ) ,求b x , y b_{x, y} b x , y .
思路(此方法几乎不需要任何知识储备,但思维难度较高)
Step0首先应考虑如何减小计算次数.
依异或运算的性质x ⊕ x = 0 x \oplus x=0 x ⊕ x = 0 ,每一个数至多计算一次.(显然的)
我们首先想到将b x , y b_{x, y} b x , y 化到第一行计算,容易知道,计算b x , y b_{x, y} b x , y 只需要用到第一行的b 0 , y , b 0 , y + 1 , … , b 0 , y + x b_{0, y},~b_{0, y+1},~\dots,~b_{0, y+x} b 0 , y , b 0 , y + 1 , … , b 0 , y + x 这些数.哪些数需要计算呢?我们可以通过 在异或表达式中出现的次数 进行判断,如出现奇数次,就需要计算,如果是偶数次,那么就消掉了,不需要计算.
举个栗子,计算b 2 , 2 = b 1 , 2 ⊕ b 1 , 3 = b 0 , 2 ⊕ b 0 , 3 ⊕ b 0 , 3 ⊕ b 0 , 4 b_{2,2}=b_{1,2} \oplus b_{1,3}=b_{0,2} \oplus b_{0,3} \oplus b_{0,3} \oplus b_{0,4} b 2 , 2 = b 1 , 2 ⊕ b 1 , 3 = b 0 , 2 ⊕ b 0 , 3 ⊕ b 0 , 3 ⊕ b 0 , 4 ,它实际上等于b 0 , 2 ⊕ b 0 , 4 b_{0,2} \oplus b_{0,4} b 0 , 2 ⊕ b 0 , 4 .我们称b 0 , 3 b_{0,3} b 0 , 3 出现偶数次,b 0 , 2 b_{0,2} b 0 , 2 和b 0 , 4 b_{0,4} b 0 , 4 出现奇数次.
如何判断出现的次数呢?
Step1 模二杨辉三角刚才的栗子中,b 0 , 2 , b 0 , 3 , b 0 , 4 b_{0,2},b_{0,3},b_{0,4} b 0 , 2 , b 0 , 3 , b 0 , 4 出现的次数分别是1 , 2 , 1 1,2,1 1 , 2 , 1 ,它是杨辉三角的第2 2 2 行.
能够证明,在b x , y b_{x, y} b x , y 的异或表达式中,b 0 , y , b 0 , y + 1 , … , b 0 , y + x b_{0, y},~b_{0, y+1},~\dots,~b_{0, y+x} b 0 , y , b 0 , y + 1 , … , b 0 , y + x 出现的次数恰好是杨辉三角的第x x x 行.因此需要知道,杨辉三角中,哪些项是奇数,哪些是偶数 .
先打个表:
打表程序(最朴素的杨辉三角):
1 2 3 4 5 6 7 8 9 10 int a[88 ][88 ] = { 0 };for (int i = 0 ; i < n; ++i) { a[i][0 ] = a[i][i] = 1 ; for (int j = 1 ; j < i; ++j) a[i][j] = a[i-1 ][j] ^ a[i-1 ][j-1 ]; } for (int i = 0 ; i < n; ++i) { std::cout << '\n' << i << ":\t" ; for (int j = 0 ; j < 88 - i; ++j) std::cout << " " ; for (int j = 0 ; j < i + 1 ; ++j) std::cout << a[i][j] << " " ; }
我发现它的第2 i 2^i 2 i 行只有首末项是奇数,其它是偶数,而第2 i − 1 2^i-1 2 i − 1 行全为奇数.有一个想法是,将欲求的项化到第2 i 2^i 2 i 行之前的两项,即b x , y = b x − 2 i , y ⊕ b x − 2 i , y + 2 i b_{x,y}=b_{x-2^i,y} \oplus b_{x-2^i,y+2^i} b x , y = b x − 2 i , y ⊕ b x − 2 i , y + 2 i ,再(依二进制位)递归计算.
如x = 19 → 19 − 16 = 3 x = 3 → 3 − 2 = 1 x = 1 → 1 − 1 = 0 x = 0 x=19 \xrightarrow{19-16=3} x=3 \xrightarrow{3-2=1} x=1 \xrightarrow{1-1=0} x=0 x = 1 9 1 9 − 1 6 = 3 x = 3 3 − 2 = 1 x = 1 1 − 1 = 0 x = 0 .
这样计算的项数会比较少……吗?每有一个二进制位为1 1 1 ,计算次数就要翻倍,最坏情况需要计算2 15 2^{15} 2 1 5 次,TLE \text{TLE} TLE .
一个自然的想法是不要全部化为x = 0 x=0 x = 0 ,可以先预处理前N N N 行,化到x < N x<N x < N 时直接出结果(有点 根号分治 的想法),但依旧TLE \text{TLE} TLE .我们必须解决「计算次数就要翻倍」这个问题.
再次问这个问题:如何减小计算次数?
既然我们决定化到x < N x<N x < N 了,那到底哪一行合适?
Step2 分形借助你强大的观察力,发现,模二杨辉三角第x \boldsymbol x x 行的数是由x N \boldsymbol{\frac xN} N x 行的每两个数之间插入N − 1 \boldsymbol{N-1} N − 1 个0 \boldsymbol0 0 得到的(其中N = 2 i \boldsymbol{N=2^i} N = 2 i ).
没注意到也没关系,如果你的注意力足够……近视 的话,能发现上面那个图形是个 分形 .
分形是一种可以分成数个部分,且每一部分都是整体缩小后的形状的形态特征.
有什么用?我们可以从图中「抽出」一个图形,比如,每隔4 4 4 行4 4 4 列的数全部提取出来,依旧是一个模二杨辉三角.同样可得,第x x x 行的数是由x 4 \frac x4 4 x 行的每两个数之间插入3 3 3 个0 0 0 得到的.
1 1 1 1 0 1 1 1 1 1 1 0 0 0 1 1 1 0 0 1 1 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 \begin{array}{} {\color{green}1} \\ {\color{blue}1~1} \\ {\color{blue}1~0~1} \\ 1~1~1~1 \\ {\color{red}1}~0~0~0~{\color{red}1} \\ 1~1~0~0~1~1 \\ 1~0~1~0~1~0~1 \\ 1~1~1~1~1~1~1~1 \\ {\color{red}1}~0~0~0~{\color{red}0}~0~0~0~{\color{red}1} \\ \end{array} 1 1 1 1 0 1 1 1 1 1 1 0 0 0 1 1 1 0 0 1 1 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1
这样的递归就快很多了:如果x = N q x=Nq x = N q (绿色的尖尖),直接看第0 0 0 行(最下面一行红色)中哪些数需要计算,一步结束.而第0 0 0 行又和蓝色的杨辉三角的最下面一行(实际上是第q q q 行的杨辉三角)相同,依据杨辉三角第q q q 行的01 01 0 1 值就能判断第0 0 0 行哪些数需要计算了.
如果带有余数x = N q + r x=Nq+r x = N q + r ,考虑子三角,化到第r r r 行,因为已经预处理前N N N 行了,第r r r 行的所有数已知,需要计算的数同理依据蓝色的杨辉三角判断.
实现前N N N 行直接算:
1 2 3 4 5 6 7 const int maxN = 3e5 + 7 , N = 512 ;int a[N][maxN];for (int j = 1 ; j < N; ++j) { for (int i = 0 ; i < n - j; ++i) { a[j][i] = a[j - 1 ][i] ^ a[j - 1 ][i + 1 ]; } }
按上面的分析,N N N 只能取2 2 2 的幂,且应该在maxN \sqrt{\text{maxN}} maxN 级别最优,因此我们取512 512 5 1 2 .
预处理模二杨辉三角:
1 2 3 4 5 6 7 8 9 const int M = 587 ;int b[M][M];b[0 ][0 ] = 1 ; for (int j = 1 ; j < M; ++j) { b[j][0 ] = b[j][j] = 1 ; for (int i = 1 ; i < j; ++i) b[j][i] = b[j - 1 ][i] ^ b[j - 1 ][i - 1 ]; }
因为计算x = N q + r x=Nq+r x = N q + r 需要用到杨辉三角的第q q q 行,q q q 的最大取值是⌈ x N ⌉ \lceil\frac{x}{N}\rceil ⌈ N x ⌉ ,因此至少要预处理这么多行,即M = ⌈ x N ⌉ = 587 M=\lceil\frac{x}{N}\rceil=587 M = ⌈ N x ⌉ = 5 8 7 .
核心部分:
1 2 3 4 5 6 7 8 9 inline int solve (int x, int y) { int q = x / N, r = x % N; int ans = 0 ; for (int i = 0 ; i <= q; ++i) { if (b[q][i] == 1 ) ans ^= a[r][i * N + y]; } return ans; }
时间复杂度Θ ( N n + M 2 + q M ) ≈ Θ ( n + q ) \Theta(Nn+M^2+qM)\approx\Theta(n+q) Θ ( N n + M 2 + q M ) ≈ Θ ( n + q ) ,实测1778ms \text{1778ms} 1778ms .
AC code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <cstdio> #define Code int main() { int T=1; #define Codes int main() { int T=read(); #define by while (T--) eachT(); #define HTLDL return 0; #define HappyHDUPKSAI } inline long long read () { char st; long long x = 0 ; int fu = 1 ; st = getchar (); while (st > 57 || st < 48 ) { if (st == 45 ) fu = -1 ; st = getchar (); }while (st <= 57 && st >= 48 ) { x = (x << 3 ) + (x << 1 ) + st - 48 ; st = getchar (); }return x * fu; }typedef long long ll;const int maxN = 3e5 + 7 , N = 512 , M = 587 ;int a[N][maxN], b[M][M];inline int solve (int y, int x) { int q = x / N, r = x % N; int ans = 0 ; for (int i = 0 ; i <= q; ++i) { if (b[q][i] == 1 ) ans ^= a[r][i * N + y]; } return ans; } void eachT () { int n = read (); for (int i = 0 ; i < n; ++i) a[0 ][i] = read (); for (int j = 1 ; j < N; ++j) { for (int i = 0 ; i < n - j; ++i) a[j][i] = a[j - 1 ][i] ^ a[j - 1 ][i + 1 ]; } b[0 ][0 ] = 1 ; for (int j = 1 ; j < M; ++j) { b[j][0 ] = b[j][j] = 1 ; for (int i = 1 ; i < j; ++i) b[j][i] = b[j - 1 ][i] ^ b[j - 1 ][i - 1 ]; } int q = read (); while (q--) printf ("%d\n" , solve (read (), read ())); } Code by HTLDL HappyHDUPKSAI