题解 CF1713F【Lost Array】
欣赏:杨辉三角模 2。
纪念独立做出的第一道 *2900。人类智慧找规律做法。
problem
一个 \(n\) 的数列 \(a\),给你这个数列做 \(n\) 次前缀异或和的结果,还原 \(a\)。\(n\leq 10^5\)。
solution
手算一下 \(a\) 的各项“系数”(异或多少次),例如 \(n=3\) 的“系数”为 \(\{111,101,011\}\) 也就是说 \(b=\{a_1\oplus a_2\oplus a_3,a_1\oplus a_3,a_2\oplus a_3\}\)。类似的我们可以得到这样的一个系数矩阵:
1111111111111111
0101010101010101
0011001100110011
0001000100010001
0000111100001111
0000010100000101
0000001100000011
0000000100000001
0000000011111111
0000000001010101
0000000000110011
0000000000010001
0000000000001111
0000000000000101
0000000000000011
0000000000000001
我们知道这个是组合数对 2 取模后的表,我们不关注这个。只看这个表,它有如下性质:
- 若 \(n=2^k\),将系数矩阵划分成四份,其中左下角为空,其它三份完全一致。
- 对于这其它的三份,它们也有如此的性质。
- 重标号:从右往左数 \(0,1,2,3,\cdots\),从上往下数 \(0,1,2,3,\cdots\)。
考虑消元法求解这个问题。考虑 \(n=2^k\) 时,将前 \(2^{k-1}\) 行与后 \(2^{k-1}\) 行异或,得到只有左上角的矩阵,可以继续递归求出 \(a_{n/2},\cdots,a_{n-1}\);同理,后 \(2^{k-1}\) 行可以继续递归下去,得到右下角的答案 \(a_0,\cdots,a_{n/2-1}\)。这样得到了一个 \(H(n)=2H(n/2)+O(n)=O(n\log n)\) 的算法。
若 \(n\neq 2^k\),我们找一个最大的 \(k\) 使得 \(n=2^k+b\)。考虑将问题分成两步:
- 用下面的 \(b\) 行异或上面的 \(b\) 行,因为左下角是空的,所以异或后上面 \(b\) 行只剩下 \(a_{2^k},\cdots,a_{n-1}\) 的数字,可以递归求解。
- 消去左边刚刚求过的部分,对 \(a_0,\cdots,a_{2^k-1}\) 这一部分用刚刚说的方法做。
考虑怎么消元:直接求是 \(O(n^2)\) 的,但是我们一样可以分治,求出左上角和右下角,合并一下,复杂度 \(T(n)=2T(n/2)+O(n)=O(n\log n)\) 并不爆炸。
所以总的时间复杂度是 \(G(n)=G(n/2)+T(n)+H(n)=G(n/2)+O(n\log n)=O(n\log n)\)。
code
点击查看代码
#include <cstdio>
#include <vector>
#include <cstring>
#include <cassert>
#include <algorithm>
using namespace std;
typedef long long LL;
int n,ret[1<<19],lg[1<<19];
vector<int> reduce(int L,int R,int x,int y){
if(L==R) return {x<=L&&L<=y?ret[L]:0};
int mid=(L+R)>>1;
vector<int> up=reduce(L,mid,x,y),down=reduce(mid+1,R,x,y),ans={};
int len=R-L+1;
for(int i=0;i<len/2;i++) ans.push_back(up[i]^down[i]);
for(int i=0;i<len/2;i++) ans.push_back(down[i]);
return ans;
}
void calc(vector<int> val,int L,int R){
// printf("calc(%d,%d)\n",L,R);
// assert(val.size()==R-L+1);
// printf("{");
// for(int x: val) printf("%d,",x);
// printf("}\n");
if(L==R) return void(ret[L]=val.front());
int mid=(L+R)>>1;//[L,mid],[mid+1,R]
int len=R-L+1;
vector<int> down={};
for(int i=len/2;i<len;i++) down.push_back(val[i]);
calc(down,mid+1,R);
vector<int> up={};
for(int i=0;i<len/2;i++) up.push_back(val[i]^val[i+len/2]);
calc(up,L,mid);
}
void solve(vector<int> val,int L,int R){
// printf("solve([%d,%d])\n",L,R);
// assert(val.size()==R-L+1);
// printf("{");
// for(int x: val) printf("%d,",x);
// printf("}\n");
int k=1<<lg[R-L+1],len=R-L+1;
if(R-L+1==k) return calc(val,L,R);
vector<int> sub={};
for(int i=k;i<len;i++) sub.push_back(val[i-k]^val[i]);
solve(sub,L,L+len-k-1);
vector<int> rec=reduce(L+len-k-k,L+len-k-1,L,L+len-k-1);
// printf("rec:");
// for(int x: rec) printf("%d,",x);puts("");
for(int i=k;i<len;i++) val.pop_back();
for(int i=0;i<k;i++) val[i]^=rec[i];
calc(val,L+len-k,R);
}
int main(){
scanf("%d",&n);
lg[0]=-1; for(int i=1;i<=n;i++) lg[i]=lg[i>>1]+1;
vector<int> val={};
for(int i=0,x;i<n;i++) scanf("%d",&x),val.push_back(x);
solve(val,0,n-1);
for(int i=0;i<n;i++) printf("%d%c",ret[i]," \n"[i==n-1]);
return 0;
}
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://www.cnblogs.com/caijianhong/p/solution-CF1713F.html