题解 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\)。考虑将问题分成两步:

  1. 用下面的 \(b\) 行异或上面的 \(b\) 行,因为左下角是空的,所以异或后上面 \(b\) 行只剩下 \(a_{2^k},\cdots,a_{n-1}\) 的数字,可以递归求解。
  2. 消去左边刚刚求过的部分,对 \(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;
}
posted @ 2022-11-15 17:37  caijianhong  阅读(18)  评论(0编辑  收藏  举报