10.15 2025多校CSP模拟赛5/CSP-S模拟32 改题记录

HZOJ

写在前面

大概就是罚坐场吧。整场都在XoXzzzzz。T1差点抢到首A,奈何不给我大样例写挂了。还好最后补了大样例保险点写个程序在1000个答案中check到了一个错误的。然后后面就纯纯罚坐,啥也不会写,啥也不想写。然后大概就是T4改了inf小时终于改出来了吧。

《You & I》

望着时钟 那窃窃私语的秘密
我心里那些最可渴望的故事
可以抹掉现在我的摸样也没关系
有人催促我也没关系
请呼唤我的名字
害怕内心的秘密 透过手指的缝隙曝光
胸口压抑地难受
在忍忍等着我吧
你和我现在还不可以
虽然想让时钟走快点
在你生活着的未来世界
请呼唤我的名字
在我偷看的未来时光里
你和我在一起
喜欢和我一起玩的你
我问你的话 你也喜欢
我的名字叫什么
眨眼间长大成人
你会认得我吧 你会记得我吧
那么神奇的孩子
好喜欢透过指缝看见你的样子
用指尖拨动 指针啊 奔跑吧
再走快一点
紧闭双眼 施展魔法
你和我还差一点点就能见面
虽不知道是在何日何时
在那你存在的未来
如果看到我在徘徊
为了让我认出你
请呼唤我的名字

A. 小 Z 爱计数

签到题。注意到排序后相邻两个操作后的值的差的绝对值一定不大于两个操作的差。小了的可以用归零调整。但是如果不能归零调整的话必须满足绝对值之差减去操作数之差是偶数,因为只有这样才能不断+1-1调整,否则只能到相邻点位。再次感谢大样例,不然就0pts了qwq。

B. 小 Z 爱划分

dp+式子转化就是王炸。题意是给出一个序列,可以对序列进行任意的区间划分,每个区间的权值为区间内数的异或和,每种划分方式的权值为所有区间的权值和。求问所有划分方法的平方和(平方的和)。

首先考虑部分分。令\(dp_i\) 为前\(i\) 项对答案的贡献,第\(i\) 项前缀异或和为\(a_i\)。考虑拆贡献,考虑将序列平方和拆成每段和平方的积。那么可以容易地推出$ dp_i=\sum_{j=0}^{i-1} dp_j*(a_i^a_j)。时间复杂度\(O(n^2)\)

然后发现目前看来平方是一个比较棘手的东西,所以考虑一下简化版问题————没有平方的情况。异或和只有在相异或的两个数二进制下同一位数不同时该位才为1。然后异或和也能拆成若干个2的整数次幂相加。所以每个二进制位对答案的贡献是独立的。考虑拆每个二进制位的贡献。我们记录好每个二进制位为某个值的\(dp_i\) 的和。转移到某个位置了,枚举每个二进制位的贡献即可。每一位的贡献就是2的对应幂次乘上\(dp_j\)。时间复杂度\(O(nlogn)\)

考虑平方和的性质。平方和就是所有求和的数所有两两组合相乘,再将所有组合相加得到的值。然后同底数幂相乘底数不变指数相加。所以我们可以考虑人工组合。就是分别枚举每个数的两个二进制位。然后再在转移时同时更新\(w_{i,j,0/1,0/1}\),记录二进制下第\(i\) 位和第\(j\) 位分别对应相应数时的\(dp_j\) 的和。参考上面的做法就能\(O(nlog^2n)\) 通过。

代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10,mod=1e9+7;
typedef long long ll;
int a[maxn],dp[maxn];
int w[32][32][2][2],qp[64];
int main(){
	freopen("partition.in","r",stdin);
	freopen("partition.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int T;
	qp[0]=1;
	for(int i=1;i<=63;i++) qp[i]=(qp[i-1]<<1)%mod;
	cin>>T;
	while(T--){
		int n;
		cin>>n;
		for(int t1=1;t1<=31;t1++)
			for(int t2=1;t2<=31;t2++) w[t1][t2][0][0]=1,w[t1][t2][0][1]=w[t1][t2][1][0]=w[t1][t2][1][1]=0;
		for(int i=1;i<=n;i++) cin>>a[i],a[i]^=a[i-1];
		for(int i=1;i<=n;i++){
			dp[i]=0;
			for(int t1=1;t1<=31;t1++)
				for(int t2=1;t2<=31;t2++) dp[i]=(dp[i]+1ll*qp[t1+t2-2]*w[t1][t2][((a[i]>>(t1-1))&1)^1][((a[i]>>(t2-1))&1)^1])%mod;
			for(int t1=1;t1<=31;t1++)
				for(int t2=1;t2<=31;t2++) w[t1][t2][(a[i]>>(t1-1))&1][(a[i]>>(t2-1))&1]=(w[t1][t2][(a[i]>>(t1-1))&1][(a[i]>>(t2-1))&1]+dp[i])%mod;	
		}
		cout<<dp[n]<<'\n';
	}
	return 0;
}

C. 小 Z 爱优化

又是一道dp。。。题意是给出长为\(n\) 的序列\(a\),可以将相邻两个数划分为一组,也能一个数单独为一组,每个数必须属于某一个组。每组的权值是组内的数之和。求问所有划分方式中极差最小的方式的极差是多少。

考虑部分分的写法。枚举最小值,然后最小化最大值。转移式子就是$ dp_i=min{max(dp_{i-1},a_i),max(dp_{i-2},a_i+a_{i-1})}。由于题目限制,最小值最多有2n-1种,所以复杂度是\(O(n^2)\) 的。

继续这种想法。由于一定存在一种方案使得极差最小,我们考虑如何达到。我们可以将每一组类比为一块骨牌,骨牌长度可以为1或2。每张骨牌有一个权值,易知最多有2n-1种权值。我们要用这些骨牌填满长为\(n\) 的一个区间,同时最小化极差。容易知道最优方案下一定会存在一些骨牌不会被用到。考虑将序列转到线段树上,并逐步舍弃一些当前状态下肯定不会再用的骨牌。令\(t_{u,0/1,0/1}\)\(u\) 节点左右子节点分别是否与对方同一组的最小化最大值。初始化就将每个叶子节点\((0,0)\) 赋为\(a_l\)\((0,1) ← a_l+a_{l+1}, (1,0) ← a_l+a_{l-1}, (1,1) ← inf\)。pushup的时候就将该节点的值赋为子节点连接情况的最大值最小的方案即可。该状态下的答案就是\(t_{1,0,0}-a_{now}\)。然后每用完一张骨牌将其对应叶子节点赋为inf即可。

代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+10,inf=INT64_MAX;
int a[maxn],dp[maxn];
#define lc(u) (u<<1)
#define rc(u) (u<<1|1)
int t[maxn<<2][2][2];
inline void pushup(int u){
	t[u][0][0]=min(max(t[lc(u)][0][0],t[rc(u)][0][0]),max(t[lc(u)][0][1],t[rc(u)][1][0]));
	t[u][1][0]=min(max(t[lc(u)][1][0],t[rc(u)][0][0]),max(t[lc(u)][1][1],t[rc(u)][1][0]));
	t[u][0][1]=min(max(t[lc(u)][0][0],t[rc(u)][0][1]),max(t[lc(u)][0][1],t[rc(u)][1][1]));
	t[u][1][1]=min(max(t[lc(u)][1][0],t[rc(u)][0][1]),max(t[lc(u)][1][1],t[rc(u)][1][1]));
}
inline void build(int u,int l,int r){
	if(l==r){
		t[u][0][0]=a[l];
		t[u][0][1]=a[l]+a[l+1];
		t[u][1][0]=a[l-1]+a[l];
		t[u][1][1]=inf;
		return;
	}
	int mid=(l+r)>>1;
	build(lc(u),l,mid);
	build(rc(u),mid+1,r);
	pushup(u);
}
inline void update(int u,int l,int r,int p,bool p1,bool p2,int k){
	if(l==r){
		t[u][p1][p2]=k;
		return;
	}
	int mid=(l+r)>>1;
	if(p<=mid) update(lc(u),l,mid,p,p1,p2,k);
	else update(rc(u),mid+1,r,p,p1,p2,k);
	pushup(u);
}
pair<int,pair<int,int>> p[maxn<<1];
signed main(){
	freopen("opti.in","r",stdin);
	freopen("opti.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int T;
	cin>>T;
	while(T--){
		int n,ans=inf,top=0;
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			p[++top]={a[i],{i,0}};
			if(i!=1) p[++top]={a[i]+a[i-1],{i-1,i}};
		} 
		build(1,1,n);
		sort(p+1,p+top+1);
		for(int i=1;i<=top;i++){
			ans=min(ans,t[1][0][0]-p[i].first);
			if(!p[i].second.second) update(1,1,n,p[i].second.first,0,0,inf);
			else update(1,1,n,p[i].second.first,0,1,inf),update(1,1,n,p[i].second.second,1,0,inf);
		}		
		cout<<ans<<'\n';
	}
	return 0;
}

D. 小 Z 爱考试

重头戏来了,改了7小时呵呵哈哈哈。

题意是给定一张有向图,每个点有一个点权\(a_i\)。可以随便确定每个点的遍历顺序。当访问到一个点且其父亲节点\(b_i\) 的权值比它大,则该节点权值增加\(w_i\)。给出\(q\) 个操作,修改\(a_k\) 或者修改\(w_k\) 或者询问所有遍历顺序下\(k\) 号点的期望点权是多少。

分析题目,观察到每个节点有且仅有一条入边,所以这是基环树/森林(以下简称基环树)。其修改操作不会修改到祖先。考虑给点分分类:
1.当\(a_i<a_{b_i}\),此时无论\(b_i\) 权值是否增加,\(i\) 权值恒会增加,故答案为\(a_i+w_i\)
2.当\(a_i>=a_{b_i}+w_{b_i}\),此时无论\(b_i\) 权值是否增加,\(i\) 的权值恒不会增加,故答案为\(a_i\)
3.当\(i\) 不属于以上两类,则其取值情况要根据其祖先节点的取值情况讨论。

所以我们的问题就是如何求得第三类点的期望点权。

考虑一个第三类点到哪个祖先节点才能确定其取值。如果祖先节点还是三类点,无法确定取值。如果是第一类点则一定会逐层使其子节点权值增加;如果是第二类点,其无法使其子节点权值增加,进而无法使询问的点权值增加。特别地,当其祖先节点在一个全为第三类点的环上,那么该节点权值也一定不会增加。

我们的方向很明了了。我们考虑将基环树拆成环和树。树上的部分很好维护,树链剖分存一下每条重链上非第三类点的深度和类型即可。环上也很好维护,断环成链上线段树维护即可。

如果不考虑修改操作,那么第三类点分为两种情况:1.树上的点;2.环上的点。对于环上的点,我们直接线段树二分出离其最近的非第三类点即可。对于树上的点,我们往根节点跳时询问路径上是否经过非第三类点即可,如果没有就继续在其根节点所在环上线段树二分。

考虑上修改操作呢?父节点可能会影响子节点的状态。由于题目限制了一个点最多是三个点的父节点,所以我们可以暴力修改。环上的点很简单,直接线段树上修改即可。树上的点就需要一个方便插入删除查找且能动态分配内存的容器了。然后就是一下子就想到了set。对于每条重链维护一个set,每次修改操作相应地删除/插入其深度与类型的二元组。这样询问时二分深度查询即可。所以修改最坏是\(O(log)\) 的,树上查询单次是\(O(log^2)\),环上查询是\(O(log)\) 的,可以通过此题。

然后考虑如何计算有两种取值的第三类点的期望权值。考虑一个点如何才会被更改权值:当且仅当按距其最近的第一类点到它自己逐级访问的相对顺序访问才会增加。令其访问顺序为\(1-l\)。所以一个点能增加权值的概率就为:

\[\frac{\binom{n}{l}(n-l)!}{n!}=\frac{1}{l!} \]

所以对于每个询问,我们寻找其第一个非第三类点祖先的同时记录其经过的点数即可。然后记得加上不增加权值的概率乘以原权值。

然后思路貌似并不复杂,但是实现难度max。细节真的太太太太太太太多了,而且码量还巨大。

以下是犯的一些糖错:
1.对于环上的某个根节点,遍历子树时忘记限制其不在环上了;
2.线段树忘记动态建根动态开点了,拉通建的,导致本来三类点的环访问到了一类点;
3.线段树区间上界下界错误;
4.忘记了一条轻链不是直接接在重链的末尾的,直接拿重链的尾节点计算答案;
5.二分查找时忘记了只要往上跳了不用要求其深度小于当前所在点;
6.对于自环要直接把其当成第二类点,不要当成第三类点;
7.对于第一类点,输出答案记得取模%%%%%%%%qwq。

累鼠我了。。。

长达242行的代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10,mod=1e9+7,inf=INT_MAX;
int a[maxn],w[maxn],b[maxn],n;
int jc[maxn],ny[maxn];
short type[maxn],du[maxn];
inline int qpow(int x,int y){
	int res=1;
	while(y){
		if(y&1) res=1ll*res*x%mod;
		x=1ll*x*x%mod;
		y>>=1;
	}
	return res;
}
int tot,to[maxn],nxt[maxn],h[maxn];
inline void adde(int x,int y){
	to[++tot]=y;
	nxt[tot]=h[x];
	h[x]=tot;
}
queue<int> que;
bool ncircle[maxn];
int siz[maxn],son[maxn],dep[maxn];
inline void dfs1(int x,int f){
	son[x]=0;
	siz[x]=1;
	dep[x]=dep[f]+1;
	for(int i=h[x];i;i=nxt[i]){
		int y=to[i];
		if(y==b[x]||(!ncircle[y])) continue;
		dfs1(y,x);
		siz[x]+=siz[y];
		if(siz[son[x]]<siz[y]) son[x]=y;
	}
}
int top[maxn],id[maxn],cnt,rnk[maxn],bg[maxn],ed[maxn],bl[maxn];
set<pair<int,int>> s[maxn];
inline void dfs2(int x,int t){
	top[x]=t;
	if(!son[x]) return;
	dfs2(son[x],t);
	for(int i=h[x];i;i=nxt[i]){
		int y=to[i];
		if(y==b[x]||y==son[x]||(!ncircle[y])) continue;
		dfs2(y,y);
	}
}
inline void dfs3(int x,int blc){
	if(id[x]) return;
	id[x]=++cnt;
	bl[x]=blc;
	rnk[cnt]=x;
	dfs3(b[x],blc);
}
int lc[maxn<<3],rc[maxn<<3],tt,root[maxn];
bool t[maxn<<3];
inline void pushup(int u){
	t[u]=t[lc[u]]|t[rc[u]];
}
inline void build(int &u,int l,int r){
	if(l>r) return;
	if(!u) u=++tt;
	if(l==r){
		t[u]=(type[rnk[l]]!=3);
		return;
	}
	lc[u]=rc[u]=0;
	int mid=(l+r)>>1;
	build(lc[u],l,mid);
	build(rc[u],mid+1,r);
	pushup(u);
}
inline void update(int u,int l,int r,int p,int k){
	if(l==r){
		t[u]+=k;
		return;
	}
	int mid=(l+r)>>1;
	if(p<=mid) update(lc[u],l,mid,p,k);
	else update(rc[u],mid+1,r,p,k);
	pushup(u);
}
inline int qry(int u,int ql,int qr,int l,int r,bool flg){
	if(!t[u]||ql>qr) return inf;
	if(l==r) return l;
	if(ql==l&&qr==r) flg=1;
	int mid=(l+r)>>1;
	if(flg){
		if(t[lc[u]]) return qry(lc[u],ql,qr,l,mid,flg);
		return qry(rc[u],ql,qr,mid+1,r,flg);
	}
	int ans=inf;
	if(ql<=mid) ans=qry(lc[u],ql,min(mid,qr),l,mid,flg);
	if(qr>mid) ans=min(ans,qry(rc[u],max(mid+1,ql),qr,mid+1,r,flg));
	return ans;
}
inline void ins(int x,int y){
	type[x]=y;
	s[top[x]].insert({dep[x],type[x]});
	if(!ncircle[x]) update(root[bl[x]],bg[bl[x]],ed[bl[x]],id[x],1);
}
inline void del(int x){
	s[top[x]].erase({dep[x],type[x]});
	if(!ncircle[x]) update(root[bl[x]],bg[bl[x]],ed[bl[x]],id[x],-1);
}
inline int getans(int x,int dis){
	++dis;
	return ((1ll*ny[dis]*(a[x]+w[x])%mod+1ll*(1-ny[dis])*a[x]%mod)%mod+mod)%mod;
}
inline int qcircle(int x,int realx,int dis){
	int y=qry(root[bl[x]],id[x]+1,ed[bl[x]],bg[bl[x]],ed[bl[x]],0);
	if(y!=inf){
		y=rnk[y];
		if(type[y]==1) return getans(realx,id[y]-id[x]+dis);
		return a[realx];
	}
	y=qry(root[bl[x]],bg[bl[x]],id[x]-1,bg[bl[x]],ed[bl[x]],0);
	
	if(y==inf) return a[realx];
	y=rnk[y];
	if(type[y]==2) return a[realx];
	return getans(realx,ed[bl[x]]-id[x]+id[y]-bg[bl[x]]+1+dis);
}
inline int jump(int from){
	int x=from;
	while(1){
		if(x==from||!s[top[x]].size()){
			x=top[x];
			if(!ncircle[x]) break;
			x=b[x];
		}
		else{
			auto y=s[top[x]].upper_bound({dep[x],2});
			if(y!=s[top[x]].begin()){
				--y;
				if((*y).second==1) return getans(from,dep[from]-((*y).first));
				return a[from];
			}
			x=top[x];
			if(!ncircle[x]) break;
			x=b[x];
		} 
	}
	return qcircle(x,from,dep[from]-dep[x]);
}
inline int qncircle(int x){
	if(s[top[x]].size()){
		auto y=s[top[x]].lower_bound({dep[x],-1});
		if(y!=s[top[x]].begin()){
			y--;
			if((*y).second==1) return getans(x,dep[x]-((*y).first));
			return a[x];
		}
	}
	return jump(x);
}
signed main(){
	freopen("exam.in","r",stdin);
	freopen("exam.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	jc[0]=ny[0]=1;
	for(int i=1;i<=2e5+1;i++) jc[i]=1ll*jc[i-1]*i%mod,ny[i]=qpow(jc[i],mod-2);
	int T;
	cin>>T;
	while(T--){
		int q,blc=0;
		cin>>n>>q;
		tt=cnt=tot=0;
		for(int i=1;i<=n;++i) bl[i]=top[i]=bg[i]=ed[i]=b[i]=root[i]=du[i]=type[i]=dep[i]=rnk[i]=id[i]=h[i]=ncircle[i]=0,s[i].clear();
		for(int i=1;i<=n;++i){
			cin>>a[i]>>b[i]>>w[i];
			++du[b[i]];
		} 
		for(int i=1;i<=n;++i){
			if(b[i]==i){
				type[i]=2;
				continue;
			}
			adde(b[i],i);
			if(!du[i]) ncircle[i]=1,que.push(i);
			if(a[i]>=a[b[i]]+w[b[i]]) type[i]=2;
			else if(a[i]<a[b[i]]) type[i]=1;
			else type[i]=3;
		}
		while(que.size()){
			int x=que.front();
			que.pop();
			--du[b[x]];
			if(!du[b[x]]) ncircle[b[x]]=1,que.push(b[x]);
		}
		for(int i=1;i<=n;++i)
			if(!ncircle[i]) dfs1(i,0),dfs2(i,i);
		for(int i=1;i<=n;++i)
			if(!ncircle[i]&&!id[i]){
				bg[++blc]=cnt+1;
				dfs3(i,blc);
				ed[blc]=cnt;
				build(root[blc],bg[blc],ed[blc]);
			}
		for(int i=1;i<=n;i++){
			if(type[i]!=3) s[top[i]].insert({dep[i],type[i]});
		}
		for(int i=1,opt,x,y;i<=q;++i){
			cin>>opt;
			if(opt==1){
				cin>>x>>y;
				if(type[x]!=3) del(x);
				a[x]=y;
				if(a[x]>=a[b[x]]+w[b[x]]) ins(x,2);
				else if(a[x]<a[b[x]]) ins(x,1);
				else type[x]=3;
				for(int j=h[x];j;j=nxt[j]){
					int y=to[j];
					if(type[y]!=3) del(y);
					if(a[y]>=a[x]+w[x]) ins(y,2);
					else if(a[y]<a[x]) ins(y,1);
					else type[y]=3;
				}
			}
			else if(opt==2){
				cin>>x>>y;
				w[x]=y;
				for(int j=h[x];j;j=nxt[j]){
					int y=to[j];
					if(type[y]!=3) del(y);
					if(a[y]>=a[x]+w[x]) ins(y,2);
					else if(a[y]<a[x]) ins(y,1);
					else type[y]=3;
				}
			}
			else{
				cin>>x;
				if(type[x]==1) cout<<(a[x]+w[x])%mod<<'\n';
				else if(type[x]==2) cout<<a[x]<<'\n';
				else if(!ncircle[x]) cout<<qcircle(x,x,0)<<'\n';
				else cout<<qncircle(x)<<'\n';
			}
		}
	}
	return 0;
}
posted @ 2025-10-16 15:57  _dlwlrma  阅读(30)  评论(1)    收藏  举报