CSP 2025 游记&总结

T1

大约开考 40 min 后做出,有点慢了。

反悔贪心,按照每个人最大的满意度与次大值的差为第一关键字,次大值与最小值的差为第二关键字从大到小排序,然后挨个取就行了。

期望得分:\(100\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define MAXN 100005
using namespace std;
const int inf=1e18;
int T,n,cnt[4],ans;
struct Node{
	int v,id;
}t[4];
bool cmpt(Node x,Node y){
	return x.v>y.v;
}
struct AC{
	int a,b,c,va,vb,vc;
}a[MAXN];
bool cmp(AC x,AC y){
	return x.a-x.b==y.a-y.b?x.b-x.c>y.b-y.c:x.a-x.b>y.a-y.b;
}
signed main(){
	//freopen("club.in","r",stdin);
	//freopen("club.out","w",stdout);
	scanf("%lld",&T);
	while(T--){
		scanf("%lld",&n);
		for(int i=1;i<=n;i++){
			scanf("%lld%lld%lld",&t[1].v,&t[2].v,&t[3].v);
			t[1].id=1,t[2].id=2,t[3].id=3;
			sort(t+1,t+3+1,cmpt);
			a[i].a=t[1].v,a[i].b=t[2].v,a[i].c=t[3].v;
			a[i].va=t[1].id,a[i].vb=t[2].id,a[i].vc=t[3].id;
		}
		sort(a+1,a+n+1,cmp);
		cnt[1]=cnt[2]=cnt[3]=0;
		ans=0;
		for(int i=1;i<=n;i++){
			if(cnt[a[i].va]<n/2)ans+=a[i].a,cnt[a[i].va]++;
			else if(cnt[a[i].vb]<n/2)ans+=a[i].b,cnt[a[i].vb]++;
			else ans+=a[i].c,cnt[a[i].vc]++;
		} 
		printf("%lld\n",ans);
	}
	return 0;
}

T2

做完 T1 后先想了约 90 min,打完 T3 暴力后又做了 60 min,直到考试结束。本题其实并不难,但选手在考场上的抗压能力和应变能力还是有待提高。

考场上想到了直接枚举每个新点是否要建,也想到了只有原图最小生成树中的边是有用的这一结论,以上加上看题时间和写部分分约 30 min。然后选手竟然卡在了新点当正常点跑最小生成树这一步,等想出来距离结束只有 60 min 了。然后选手打了 \(O(m\log m+2^knk\log nk)\) 做法但竟然没有调出来,最后只得 48 pts 离场。

期望得分:\([12,48]\)

正解其实只有一步之遥,把原图最小生成树 \(n-1\) 条边和 \(nk\) 条新边预排序,若端点没有建起来就直接不管,复杂度 \(O(m\log m+nk\log nk+2^knk)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define N 10005
#define M 1000005
#define K 15
using namespace std;
const int inf=1e18;
int r[N+K],n,m,k,c[K],a[K][N],ans;
struct Node{
	int u,v,w;
}e[M+N*K],E[M];
int mcnt;
bool cmp(Node x,Node y){
	return x.w<y.w;
}
int find(int x){
	if(r[x]!=x)r[x]=find(r[x]);
	return r[x];
}
bool merge(int x,int y){
	if(find(x)==find(y))return 0;
	r[find(x)]=find(y);
	return 1;
}
bool b[K];
void dfs(int x){
	if(x==k+1){
		int sum=0;
		for(int i=1;i<=n+k;i++)r[i]=i;
		for(int i=1;i<=k;i++){
			if(b[i])sum+=c[i];
		}
		for(int i=1;i<=mcnt;i++){
			int u=e[i].u,v=e[i].v;
			if(u>n){
				if(!b[u-n])continue;
			}
			if(merge(u,v)){
				sum+=e[i].w;
			}
		}
		ans=min(ans,sum);
		return;
	}
	dfs(x+1);b[x]=1;
	dfs(x+1);b[x]=0;
}
signed main(){
	//freopen("road.in","r",stdin);
	//freopen("road.out","w",stdout);
	scanf("%lld%lld%lld",&n,&m,&k);
	for(int i=1;i<=m;i++){
		scanf("%lld%lld%lld",&E[i].u,&E[i].v,&E[i].w);
	}
	for(int i=1;i<=k;i++){
		scanf("%lld",&c[i]);
		for(int j=1;j<=n;j++)scanf("%lld",&a[i][j]),e[++mcnt]=(Node){i+n,j,a[i][j]};
	}
	sort(E+1,E+m+1,cmp);
	for(int i=1;i<=n;i++)r[i]=i;
	for(int i=1;i<=m;i++){
		if(merge(E[i].u,E[i].v)){
			e[++mcnt]=E[i];
		}
	}
	sort(e+1,e+mcnt+1,cmp);
	ans=inf;
	dfs(1);
	printf("%lld\n",ans);
	return 0;
}

T3

开考后约 120 min 开始思考,一开始往正解方向想,考虑到可能要使用 AC 自动机,但没有想到字符串的替换修改要如何处理,最后打了哈希暴力,且由于看题不仔细和码力有限打了约 40 min。

期望得分:\([10,30]\)

正解其实并不难,考虑 \(s1\)\(s2\) 可以视为 \(ABC\) 变为 \(ADC\),那就直接拼起来变成 \(A?BD?A\),其中 \(?\) 是特殊字符,这样就可以进行匹配和识别了,\(t1\)\(t2\) 同理,在 AC 自动机上跑多模匹配即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define MAXN 5000005
using namespace std;
const int inf=1e18;
char ca[MAXN],cb[MAXN],tc[MAXN];
int n,q,num=1,d[MAXN];
struct ACAM{
	int son[27],fail,sum;
	vector<int>v;
}t[MAXN];
int gc(char c){
	if(c>='a'&&c<='z')return c-'a';
	else return 26;
}
void insert(char *s){
	int len=strlen(s+1),x=1;
	for(int i=1;i<=len;i++){
		int y=gc(s[i]);
		if(!t[x].son[y])t[x].son[y]=++num;
		x=t[x].son[y];
	}
	t[x].sum++;
}
void getfail(){
	queue<int>q;
	for(int i=0;i<=26;i++)t[0].son[i]=1;
	q.push(1);
	while(!q.empty()){
		int x=q.front();q.pop();
		int f=t[x].fail;
		for(int i=0;i<=26;i++){
			int y=t[x].son[i];
			if(!y){
				t[x].son[i]=t[f].son[i];
				continue;
			}
			t[y].fail=t[f].son[i];d[t[y].fail]++;
			q.push(y);
		}
	}
}
void topsort(){
	queue<int>q;
	for(int i=1;i<=num;i++)if(!d[i])q.push(i);
	while(!q.empty()){
		int x=q.front(),y=t[x].fail;q.pop();
		d[y]--;
		t[y].v.push_back(x);
		if(!d[y])q.push(y);
	}
}
void dfs(int x){
	for(int i=0;i<t[x].v.size();i++){
		int y=t[x].v[i];
		t[y].sum+=t[x].sum;
		dfs(y);
	}
}
int query(char *s){
	int len=strlen(s+1),x=1,ans=0;
	for(int i=1;i<=len;i++){
		int y=gc(s[i]);
		x=t[x].son[y];
		ans+=t[x].sum;		
		//cout<<s[i]<<' '<<x<<' '<<t[x].sum<<' '<<ans<<"\n";
	}
	return ans;
}
signed main(){
	//freopen("replace.in","r",stdin);
	//freopen("replace.out","w",stdout);
	scanf("%lld%lld",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%s",ca+1);scanf("%s",cb+1);
		int na=strlen(ca+1),nb=strlen(cb+1),l=0,r=na+1;
		for(int j=1;j<=na;j++){
			if(ca[j]!=cb[j]){
				l=j;
				break;
			}
		}
		for(int j=na;j;j--){
			if(ca[j]!=cb[j]){
				r=j;
				break;
			}
		}
		int lcnt=0;
		for(int i=1;i<l;i++)tc[++lcnt]=ca[i];
		tc[++lcnt]='#';
		for(int i=l;i<=r;i++)tc[++lcnt]=ca[i];
		for(int i=l;i<=r;i++)tc[++lcnt]=cb[i];
		tc[++lcnt]='#';
		for(int i=r+1;i<=na;i++)tc[++lcnt]=ca[i];
		tc[lcnt+1]='\0';
		insert(tc);
	}
	getfail();
	topsort();
	dfs(1);
	for(int i=1;i<=q;i++){
		scanf("%s",ca+1);scanf("%s",cb+1);
		int na=strlen(ca+1),nb=strlen(cb+1),l=0,r=na+1;
		if(na!=nb){
			printf("0\n");
			continue;
		}
		for(int j=1;j<=na;j++){
			if(ca[j]!=cb[j]){
				l=j;
				break;
			}
		}
		for(int j=na;j;j--){
			if(ca[j]!=cb[j]){
				r=j;
				break;
			}
		}
		int lcnt=0;
		for(int i=1;i<l;i++)tc[++lcnt]=ca[i];
		tc[++lcnt]='#';
		for(int i=l;i<=r;i++)tc[++lcnt]=ca[i];
		for(int i=l;i<=r;i++)tc[++lcnt]=cb[i];
		tc[++lcnt]='#';
		for(int i=r+1;i<=na;i++)tc[++lcnt]=ca[i];
		tc[lcnt+1]='\0';
		printf("%lld\n",query(tc));
	}
	return 0;
}

T4

基本没什么时间看,最后连 12 pts 暴力都没时间打了,重大失误。

期望得分:\(0\)

正解是 \(O(n^3)\) 的 dp。设 \(f_{i,j,k}\) 表示考虑前 \(i\) 人,有 \(j\) 人面试失败,其中有 \(k\) 人是因为耐心不足放弃的。记 \(a_i\) 表示满足 \(c_j=i\)\(j\) 的个数,\(pre\)\(a\) 的前缀和。

对于 \(s_{i+1}=1\)

首先如果下一个要面试成功,那就在 \(c_{x}\) 中找一个大于 \(j\) 的(此时先不计选这个的方案数,在后面延迟贡献),需要条件 \(n-pre_{j}>i-k\),有

\[f_{i,j,k}\to f_{i+1,j,k} \]

如果面试失败,那就必须在前面未被决策的点中选一个,并枚举要在 \([1,i]\) 区间中有 \(>j'\) 的需求且不在已经决策的 \(k\) 个中选 \(l\) 个用 \(a_{j+1}\) 进行贡献,并决定 \(i-k\) 个哪些是这 \(l\) 个,以及这 \(l\) 个的顺序,有

\[f_{i,j,k}\times(pre_j-k)\times{{a_{j+1}}\choose{l}}\times{{i-k}\choose{l}}\times l!\to f_{i+1,j+1,k+l} \]

对于 \(s_{i+1}=0\)

如果这个人是参加了面试但失败,那首先必须满足耐心大于 \(j+1\) 的人大于 \(i-k-l\),若满足条件则有

\[f_{i,j,k}\times{{a_{j+1}}\choose{l}}\times{{i-k}\choose{l}}\times l!\to f_{i+1,j+1,k+l} \]

如果这个人已经被吓死了没有参加面试,那当前点就必须立即决策,即在剩下的 \(pre_{j+1}-k-l\) 中选一个放在当前位置,有

\[f_{i,j,k}\times{{a_{j+1}}\choose{l}}\times{{i-k}\choose{l}}\times l! \times (pre_{j+1}-k-l)\to f_{i+1,j+1,k+l+1} \]

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define MAXN 505
using namespace std;
const int inf=1e18,mod=998244353;
int n,m,f[MAXN][MAXN],g[MAXN][MAXN],fac[MAXN],c[MAXN],a[MAXN],pre[MAXN],s[MAXN],C[MAXN][MAXN];
char ts[MAXN];
void add(int &x,int y){
	x=(x+y)%mod;
}
signed main(){
	//freopen("employ.in","r",stdin);
	//freopen("employ.out","w",stdout);
	scanf("%lld%lld",&n,&m);
	scanf("%s",ts+1);
	for(int i=1;i<=n;i++)s[i]=ts[i]-'0';
	for(int i=1;i<=n;i++){
		scanf("%lld",&c[i]);
		a[c[i]]++;
	}
	pre[0]=a[0];
	for(int i=1;i<=n;i++)pre[i]=pre[i-1]+a[i];
	C[0][0]=fac[0]=1;
	for(int i=1;i<=n;i++){
		C[i][0]=C[i][i]=1;fac[i]=fac[i-1]*i%mod;
		for(int j=1;j<i;j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
	}
	f[0][0]=1;
	for(int i=0;i<n;i++){
		for(int j=0;j<=i;j++){
			for(int k=0;k<=min(pre[j],i);k++){
				if(s[i+1]){
					if(n-pre[j]>i-k)add(g[j][k],f[j][k]);
					for(int l=0;l<=min(a[j+1],i-k);l++){
						add(g[j+1][k+l+1],C[a[j+1]][l]*C[i-k][l]%mod*fac[l]%mod*(pre[j]-k)%mod*f[j][k]%mod);
						//cout<<C[a[j+1]][l]*C[i-k][l]%mod*fac[l]%mod*(pre[j]-k)%mod<<"\n";
					}
				}else{
					for(int l=0;l<=min(a[j+1],i-k);l++){
						if(n-pre[j+1]>i-k-l)add(g[j+1][k+l],C[a[j+1]][l]*C[i-k][l]%mod*fac[l]%mod*f[j][k]%mod);
						add(g[j+1][k+l+1],C[a[j+1]][l]*C[i-k][l]%mod*fac[l]%mod*(pre[j+1]-k-l)%mod*f[j][k]%mod);
					}
				}
			}
		}
		for(int j=0;j<=n;j++){
			for(int k=0;k<=n;k++)f[j][k]=g[j][k],g[j][k]=0/*,cout<<i<<' '<<j<<' '<<k<<' '<<f[j][k]<<"\n"*/;
		}
	}
	int ans=0;
	for(int i=0;n-i>=m;i++){
		add(ans,f[i][pre[i]]*fac[n-pre[i]]%mod); 
	}
	printf("%lld\n",ans);
	return 0;
}

总结

  1. 时间分配不合理,无论遇到什么情况、对自己打暴力部分分的速度有多自信都应该先打完所有题目的部分分再去想可能想不出来的正解。

  2. 抗压能力有待提升,不要过度在意自己花费的时间和别人的进度,也不要拿往年的题目难度往今年的题上套,冷静下来理智思考思路才是最重要的。如果实在没有思路就果断跳过,不能死磕。

  3. 短时间内码出代码并调试的能力不足,尤其是在高压环境下的调试速度和写代码的细心程度太低了,需要多加锻炼,模拟赛也要认真对待。

做好面对任何结果的心理准备,道阻且长。

UPD

100+32+50+4,比想象中的高?但还是坠机了,能进 NOIP 就知足吧。

posted @ 2025-11-05 19:20  CharlieCai2024  阅读(13)  评论(0)    收藏  举报