20260113 省选模拟赛

20260113 省选模拟赛

https://htoj.com.cn/cpp/oj/contest/detail?cid=22497077965824

Problem B. 树的直径

枚举直径中点。 对直径长度分类讨论:

  • 长度为奇数。那么中点一定是一条权为 \(1\) 的边。

  • 长度为偶数。中点一定是一条全为 \(0\) 的链(可能退化为一个点)。枚举两端不好做,但使用 点边容斥,点权值为 \(1\),边权值为 \(-1\),和为 \(1\)

所以 分别对每个点和每条边统计以它为中心的直径条数即可。但发现长度是奇数和偶数时,边的贡献抵消了,所以只用算点。

枚举每个点为根。设 \(f_{x,i}\)\(x\) 子树内 \(mxd=i\),最深点个数之和,\(g_{x,i}\)\(x\) 子树 \(mxd=i\) 的方案数。枚举边权,每次是个 max 卷积状物,暴力做即可,树上背包复杂度,\(O(n^2)\)

在根统计答案,设 \(h_i\) 为两条最长链长为 \(i\) 的方案数,转移仍然是 max 卷积状物。总复杂度 \(O(n^3)\)

先用前缀和把 max 卷积优化做到 \(O(a+b)\)。然后换根,每次合并周围节点时先按 \(mxd\) 从小到大排序再做。一条边只会在两端各合并一次,每次合并周围节点时 \(mxd\) 递增,和为 \(O(n)\),所以这部分总复杂度 \(O(n^2)\)

在根统计答案时,每条边也只会在两端合并一次,这部分复杂度也是 \(O(n^2)\)

int CC,n;
int fa[N];
vector<int> e[N];

void Add(int u,int v){
	e[u].push_back(v);
	e[v].push_back(u);
}

const ll mod=1e9+7;

inline ll Mod(ll x){return (x>=mod)?(x-mod):(x);}
inline void Add(ll &x,ll y){x=Mod(x+y);}

struct Node{
	ll f,g;
	
	friend Node operator + (Node x,Node y){
		return Node{Mod(x.f+y.f),Mod(x.g+y.g)};
	}
	
	friend Node operator * (Node x,Node y){
		return Node{(x.f*y.g+x.g*y.f)%mod,x.g*y.g%mod};
	}
	
	friend Node operator * (Node x,ll y){
		return Node{x.f*y%mod,x.g*y%mod};
	}
};
vector<Node> f[N],g[N],pre[N],suf[N];
ll h[N],H[N];

vector<Node> Merge(vector<Node> A,vector<Node> B){
	vector<Node> C;
	int ca=A.size(),cb=B.size();
	C.resize(max(ca,cb+1));
	for(int i=0;i<min(ca,cb+1);i++){
		if(i<cb) C[i]=C[i]+A[i]*B[i];
		if(i) C[i]=C[i]+A[i]*B[i-1];
	}
	ll sum=0;
	for(int i=1;i<ca;i++){
		if(i-1<cb) Add(sum,B[i-1].g);
		if(i>=2&&i-2<cb) Add(sum,B[i-2].g);
		C[i]=C[i]+A[i]*sum;
	}sum=0;
	for(int i=1;i<cb;i++){
		if(i-1<ca) Add(sum,A[i-1].g);
		C[i]=C[i]+B[i]*sum;
	}sum=0;
	for(int i=1;i<=cb;i++){
		if(i-1<ca) Add(sum,A[i-1].g);
		C[i]=C[i]+B[i-1]*sum;
	}
	return C;
}

void dfs1(int x,int pr){
	fa[x]=pr;
	f[x].push_back(Node{1,1});
	for(int y:e[x]){
		if(y==pr) continue;
		dfs1(y,x);
		f[x]=Merge(f[x],f[y]);
	}
}

vector<Node>& Get(int x,int y){
	return fa[y]==x?f[y]:g[x];
}

void dfs2(int x,int pr){
	sort(e[x].begin(),e[x].end(),[&](int p,int q)
		{return Get(x,p).size()<Get(x,q).size();});
	for(int y:e[x]){
		if(y==pr) continue;
		g[y].push_back(Node{1,1});
		for(int z:e[x]){
			if(z==y) continue;
			g[y]=Merge(g[y],Get(x,z));
		}
		dfs2(y,x); 
	}
}

signed main(){
	read(CC),read(n);
	for(int i=1;i<n;i++){
		int u,v;
		read(u),read(v);
		Add(u,v);
	}
	dfs1(1,0);
	dfs2(1,0);
	ll ans=0;
	for(int x=1;x<=n;x++){
		vector<Node> X;
		X.push_back(Node{1,1});
		memset(h,0,sizeof(h)); h[0]=1;
		for(int y:e[x]){
			memset(H,0,sizeof(H));
			vector<Node> W=Get(x,y);
			ll sum=0; int cw=W.size(),cx=X.size();
			for(int j=0;j<=n;j++){
				if(j<cw) Add(sum,W[j].g);
				if(j&&j-1<cw) Add(sum,W[j-1].g);
				(H[j]+=h[j]*sum)%=mod;
				if(j<cw&&j<cx) (H[j]+=X[j].f*W[j].f)%=mod;
				if(j&&j-1<cw&&j<cx) (H[j]+=X[j].f*W[j-1].f)%=mod;
			}
			memcpy(h,H,sizeof(h));
			X=Merge(X,W);
		}
		ll res=0;
		for(int i=0;i<=n;i++) Add(res,h[i]);
		Add(ans,res);
	}
	printf("%lld\n",(ans-n+mod)%mod);
	return 0;
}

Problem C. 广为人知

离线扫描线,转化为区间加 \(1\),区间历史异或和的异或和。

观察 1: \(x\) 加一等价于异或 \(x\oplus (x+1)\)

观察 2: \(x\oplus(x+1)=2^{h(x)}-1\)\(h(x)\)\(x\) 最低位 \(0\) 所在的位。

观察 3:\(i\) 时刻异或 \(v\),只会对 \(i,i+2,i+4,i+6,\cdots\) 时刻的历史异或和贡献 \(v\)

这样就只需要维护每个点奇偶时刻的异或和。

看上去不太好 polylog,就考虑分块。块长为 \(B\),需要维护整块的 \(ans_{i,0/1}\),每个点的 \(his_{i,0/1}\)

整块修改时,枚举 \(h(c_i)=k\),那么需要知道 \(c_i\equiv 2^{k}-1 \pmod{2^{k+1}}\) 的个数。根号分治,设一个阈值 \(2^K\)\(<2^K\) 的位用桶维护,\(\ge 2^K\) 的每个数只会出现 \(\frac n {2^K}\) 次,可以暴力更新。

散块修改时,先下放标记,然后暴力更改,重构块。

下放标记,\(c_i\) 更新是简单的,还需要根据 \(tag\) 来更新 \(his_i\)。我们把 \(tag_j\) 存下来,对每个 \(i\) 枚举 \(k\),若 \(c_i+tag_j\equiv 2^k-1\pmod{2^{k+1}}\) 就要异或 \(2^{k+1}-1\)。和上面类似做。

现在总复杂度 \(O(nKB+\frac{n^2K}{B}+\frac{n}{2^K})\),平衡到 \(O(n\sqrt{n}\log n)\)

考虑怎么去掉 \(\log\)

整块修改时,若我们能求出 \(w_{x}\) 表示当前 \(tag\equiv x\pmod{2^K}\) 再加一时给 \(ans\) 造成的贡献,就能 \(O(1)\) 修改。

首先需要知道 \(c_j\equiv i \pmod{2^k}\)\(j\) 个数,记为 \(f_{k,i}\)。而 \(f_{k,i}=f_{k+1,i}+f_{k+1,i+2^k}\),这是个 从低位到高位的满二叉 trie 树结构,可以 \(O(2^K)\) 求。接下来求 \(w_x\),将 \(2^{k+1}-1\) 拆为 \(2^0+2^1+\cdots+2^k\),那么 \(c_j+x\equiv 2^k-1\pmod{2^k}\)\(j\) 就给所有这样的 \(x\) 造成 \(2^k\) 的贡献。而 \(w_x\) 就是 \(2^K-1-x\) 在 trie 树上的路径权值异或和。所以从下到上求 \(f\),从上到下再求 \(w\) 即可。

下放标记时,我们也把 \(tag_j\) 放到 trie 上,然后查询 \(2^K-1-(c_i\bmod 2^K)\) 路径。

实现中可以不建出 trie,可以先对底层做蝴蝶变换(翻转二进制位),然后类似线段树更新。

const int B=128,M=B+5,D=N/B+5,K=7;

int n,a[N],lst[N],Q,Ans[N];
int c[N],C,L[D],R[D],bl[N],ans[D][2],his[N][2];
int w[D][M],tag[D],head[D][M],nxt[D][M],ele[D][M],tot[D],tg[D][2][M];
int f[M<<1],g[M<<1],tran[M],lg[M];
vector<pii> qy[N];

void Rebuild(int p){
	ans[p][0]=ans[p][1]=0;
	for(int i=L[p];i<=R[p];i++){
		ans[p][0]^=his[i][0];
		ans[p][1]^=his[i][1];
	}
	for(int i=0;i<B;i++){
		head[p][i]=0;
		tg[p][0][i]=tg[p][1][i]=0;
	}
	tot[p]=0;
	for(int i=L[p];i<=R[p];i++){
		int x=c[i]&(B-1);
		ele[p][++tot[p]]=i;
		nxt[p][tot[p]]=head[p][x];
		head[p][x]=tot[p];
	} 
	memset(f,0,sizeof(f));
	for(int i=L[p];i<=R[p];i++) f[tran[c[i]&(B-1)]|B]^=1;
	for(int i=B-1;i;i--) f[i]=f[i<<1]^f[i<<1|1];
	g[0]=0;
	for(int i=1;i<(B<<1);i++){
		g[i]=g[i>>1];
		if(i<B) g[i]^=f[i]<<lg[i];
	}
	for(int i=0;i<B;i++) w[p][i]=g[B|tran[B-i-1]];
}

void Pushdown(int p){
	if(!tag[p]) return;
	for(int t=0;t<=1;t++){
		memset(f,0,sizeof(f));
		for(int i=0;i<B;i++) f[tran[i]|B]=tg[p][t][i];
		for(int i=B-1;i;i--) f[i]=f[i<<1]^f[i<<1|1];
		g[0]=0;
		for(int i=1;i<(B<<1);i++){
			g[i]=g[i>>1];
			if(i<B) g[i]^=f[i]<<lg[i];
		}
		for(int i=L[p];i<=R[p];i++)
			his[i][t]^=g[B|tran[B-1-(c[i]&(B-1))]];
	}
	for(int i=L[p];i<=R[p];i++) c[i]+=tag[p];
	tag[p]=0;
}

void Modify1(int l,int r,int t){
	for(int i=l;i<=r;i++){
		his[i][t]^=c[i]^(c[i]+1);
		++c[i];
	}	
}

void Modify2(int p,int t){
	ans[p][t]^=w[p][tag[p]&(B-1)];
	tg[p][t][tag[p]&(B-1)]^=1;
	int x=B-1-(tag[p]&(B-1));
	for(int i=head[p][x];i;i=nxt[p][i]){
		int y=ele[p][i];
		int v=(c[y]+tag[p])^(c[y]+tag[p]+1)^(B-1);
		his[y][t]^=v,ans[p][t]^=v;
	}
	++tag[p];
}

void Update(int l,int r,int t){
	int p=bl[l],q=bl[r];
	if(p==q){
		Pushdown(p);
		Modify1(l,r,t);
		Rebuild(p);
		return;
	}
	Pushdown(p),Pushdown(q);
	Modify1(l,R[p],t),Modify1(L[q],r,t);
	for(int i=p+1;i<=q-1;i++) Modify2(i,t);
	Rebuild(p),Rebuild(q);
}

int Ask(int l,int r,int t){
	int p=bl[l],q=bl[r],res=0;
	if(p==q){
		Pushdown(p);
		for(int i=l;i<=r;i++) res^=his[i][t];
		Rebuild(p);
		return res;
	}
	Pushdown(p),Pushdown(q);
	for(int i=l;i<=R[p];i++) res^=his[i][t];
	for(int i=L[q];i<=r;i++) res^=his[i][t];
	for(int i=p+1;i<=q-1;i++)res^=ans[i][t];
	Rebuild(p),Rebuild(q);
	return res;
}

signed main(){
	read(n),read(Q);
	for(int i=1;i<=n;i++) read(a[i]);
	for(int i=1;i<=n;i+=B){
		++C; L[C]=i,R[C]=min(i+B-1,n);
		for(int j=L[C];j<=R[C];j++) bl[j]=C;
	}
	for(int i=2;i<=B;i++) lg[i]=lg[i>>1]+1;
	for(int i=1;i<B;i++) tran[i]=(tran[i>>1]>>1)|((i&1)<<(K-1));
	for(int i=1;i<=Q;i++){
		int l,r;
		read(l),read(r);
		qy[r].push_back({l,i});
	}
	for(int i=1;i<=n;i++){
		Update(lst[a[i]]+1,i,i&1);
		lst[a[i]]=i;
		for(pii j:qy[i]) Ans[j.second]=Ask(j.first,i,i&1);
	}
	for(int i=1;i<=Q;i++) printf("%d\n",Ans[i]);
	return 0;
}
posted @ 2026-01-15 07:40  XP3301_Pipi  阅读(2)  评论(0)    收藏  举报
Title