CSP 2025 游记

CSP-J 2025

没什么好说的。早上出门的时候车还被别人拦住了,出不去。于是换了一辆车走了。

T1 找出数字后排序,sort 秒了。

赛时代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=1e6;
int n;
char s[N+1+1],a[N+1];
int main(){
	freopen("number.in","r",stdin);
	freopen("number.out","w",stdout);
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	cin>>(s+1);
	for(int i=1;s[i];i++){
		if('0'<=s[i]&&s[i]<='9'){
			a[++n]=s[i];
		}
	}
	sort(a+1,a+n+1);
	for(int i=n;1<=i;i--){
		cout<<a[i];
	}
	
	cout.flush();
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T2 不想推导,直接模拟秒了。

赛时代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=10,M=10;
int n,m,score,a[N*M+1];
int main(){
	freopen("seat.in","r",stdin);
	freopen("seat.out","w",stdout);
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	cin>>n>>m;
	for(int i=1;i<=n*m;i++){
		cin>>a[i];
	}
	score=a[1];
	sort(a+1,a+n*m+1,greater<int>());
	for(int i=1,j=1,cnt=1,mode=0;cnt<=n*m;cnt++){
		if(a[cnt]==score){
			cout<<j<<' '<<i<<'\n';
			break;
		}
		switch(mode){
			case 0:
				if(i<n){
					i++;
					break;
				}else{
					j++;
					mode=1;
					break;
				}
			case 1:
				if(1<i){
					i--;
					break;
				}else{
					j++;
					mode=0;
					break;
				}
		}
	}
	
	cout.flush();
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T3 先快速打了个暴力,之后思考了一下。

一开始以为 \(k\) 不是给定值,然后发现不太好做。但是看了一下样例,就发现了。

显然的贪心是上一个区间的右端点要尽可能往左,因此只需要对于给定的 \(r\),快速判断是否存在 \(l\in[1,r-1]\) 使得 \(a_l\oplus a_{l+1}\oplus\cdots\oplus a_r=k\)。容易想到维护前缀异或和 \(\textit{pre}_i\),则 \([l,r]\) 符合条件即 \(\textit{pre}_{l-1}\oplus\textit{pre}_r=k\),即 \(\textit{pre}_{l-1}=k\oplus\textit{pre}_r\)

维护一下有效的左端点的 \(\textit{pre}_{l-1}\) 的值即可。赛时使用 unordered_set

时间复杂度 \(\mathcal O(n)\)

赛时代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
#include<unordered_set>
using namespace std;
constexpr const int N=5e5,K=1<<20;
int n,k,a[N+1],pre[N+1];
int main(){
	freopen("xor.in","r",stdin);
	freopen("xor.out","w",stdout);
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		pre[i]=pre[i-1]^a[i];
	}
	int ans=0;
	unordered_set<int>cnt;
	cnt.insert(0);
	for(int r=1,l=1;r<=n;r++){
		if(cnt.count(k^pre[r])){
			ans++;
			l=r+1;
			cnt.clear();
		}
		cnt.insert(pre[r]);
		/*
		for(int i=l;i<=r;i++){
			if(pre[i-1]==(k^pre[r])){
				ans++;
				l=r+1;
				break;
			}
		}
		*/
	}
	cout<<ans<<'\n';
	
	cout.flush();
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T4 仍然是小木棍。去年我就是被 T3 的小木棍所创,今年仍然如此……

一眼可以发现是 DP。(取模求方案数)

最开始设了一个 DP 状态:\(\textit{dp}_{i,j,k,l}\) 表示 \(a_1\sim a_i\),和为 \(j\),选择了 \(k\) 个木棍,最大值为 \(l\) 的方案数。

然后果断放弃,考虑 \(\max\) 不好维护,可以将 \(a_1,a_2,\cdots,a_n\) 从小到大排序使其单调不降。

\(a_i\) 的方案数即在 \(a_1\sim a_{i-1}\) 中选择至少两个,使得它们的和大于 \(a_i\)。容易发现若和大于 \(a_i\),则至少选择两个。因此只需要考虑 \(1\sim a_{i-1}\) 中选择的和大于 \(a_i\) 的方案数。

\(\textit{dp}_{i,j}\) 表示 \(a_1\sim a_i\),选择出来的和为 \(j\) 的方案数,转移是容易的,就是一个完全背包。但是,其实写到这里就已经写完了,但是我维护的 \(j\in\left[1,\sum\limits_{i=1}^na_i\right]\)……

正确的做法是维护 \(j\in\left[1,\max\limits_{i=1}^na_i\right]\),这样大于 \(x\) 的方案数即 \(2^i-\sum\limits_{j=1}^x\textit{dp}_{i,j}\)

于是时间复杂度是 \(\mathcal O\left(n^2V\right)\),而不是正确的 \(\mathcal O(nV)\)

赛时代码

修改为正确代码是简单的,此处不再给出。

//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=5000,V=5000,P=998244353;
int n,ans,a[N+1],dp[N*V+1];
int main(){
	freopen("polygon.in","r",stdin);
	freopen("polygon.out","w",stdout);
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	sort(a+1,a+n+1);
	dp[0]=1;
	for(int i=1,sum=0;i<=n;i++){
		for(int j=a[i]+1;j<=sum;j++){
			ans=(ans+dp[j])%P;
		}
		sum+=a[i];
		for(int j=sum;a[i]<=j;j--){
			dp[j]=(dp[j]+dp[j-a[i]])%P;
		}
	}
	cout<<ans<<'\n';
	
	cout.flush();
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}

最终得到了 \(100+100+100+80=380\text{pts}\),遗憾离场。

CSP-S 2025

先读了一下 T1、T2、T3、T4 的题面,感觉 T2 比较好写,先写了个暴力。

之后看 T1,经过一些思考发现至多有 \(1\) 个部分会超过一半。那么考虑先将所有的都丢到最优的部门里,如果存在一个部门超过一半,就按照「偏移量」(最大值减去次大值)最小的开始丢到其他部门里,类似于可反悔贪心。

赛时代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
#include<set>
using namespace std;
constexpr const int N=1e5;
int n,a[N+1][4],c[N+1];
int main(){
	freopen("club.in","r",stdin);
	freopen("club.out","w",stdout);
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	int T;
	cin>>T;
	while(T--){
		cin>>n;
		int ans=0;
		vector<int>s[4];
		for(int i=1;i<=n;i++){
			cin>>a[i][1]>>a[i][2]>>a[i][3];
			if(a[i][1]>=max(a[i][2],a[i][3])){
				ans+=a[i][1];
				s[1].push_back(i);
				c[i]=a[i][1]-max(a[i][2],a[i][3]);
			}else if(a[i][2]>=max(a[i][1],a[i][3])){
				ans+=a[i][2];
				s[2].push_back(i);
				c[i]=a[i][2]-max(a[i][1],a[i][3]);
			}else if(a[i][3]>=max(a[i][1],a[i][2])){
				ans+=a[i][3];
				s[3].push_back(i);
				c[i]=a[i][3]-max(a[i][1],a[i][2]);
			}
		}
		vector<int>f;
		if(s[1].size()*2>n){
			for(int i:s[1]){
				f.push_back(c[i]);
			}
			sort(f.begin(),f.end());
			for(int i=0;i<s[1].size()-n/2;i++){
				ans-=f[i];
			}
		}else if(s[2].size()*2>n){
			for(int i:s[2]){
				f.push_back(c[i]);
			}
			sort(f.begin(),f.end());
			for(int i=0;i<s[2].size()-n/2;i++){
				ans-=f[i];
			}
		}else if(s[3].size()*2>n){
			for(int i:s[3]){
				f.push_back(c[i]);
			}
			sort(f.begin(),f.end());
			for(int i=0;i<s[3].size()-n/2;i++){
				ans-=f[i];
			}
		}
		cout<<ans<<'\n';
	}
	
	cout.flush();
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}

之后看 T2,发现 \(k\leq10\),于是发现复杂度带 \(2^k\) 是比较正确的。之后进行了长时间的玄学卡常,最终获得了 \(72\text{pts}\)

赛时代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
typedef long long ll;
constexpr const int N=1e4,M=1e6,K=10;
int n,m,k,c[K+1],a[K+1][N+1];
ll ans=1ll<<62;
struct edge{
	int u,v,w;
}e[M+K*N+1],backup[M+K*N+1];
bool mode[K+1];
int f[N+K+1],size[N+K+1];
int find(int x){
	if(f[x]==x){
		return x;
	}else{
		return f[x]=find(f[x]);
	}
}
void solve(){
	if(clock()>=0.95*CLOCKS_PER_SEC){
		cout<<ans<<endl;
		exit(0);
	}
	static int id[K+1];
	int cnt=0;
	ll cost=0;
	for(int i=1;i<=k;i++){
		if(mode[i]){
			id[++cnt]=i;
			cost+=c[i];
		}
	}
	if(cost>=ans){
		return;
	}
	int mm=0;
	static edge pl[K*N+1];
	for(int i=1;i<=cnt;i++){
		for(int j=1;j<=n;j++){
			pl[++mm]={id[i]+n,j,a[id[i]][j]};
		}
	}
	sort(pl+1,pl+mm+1,[](edge a,edge b){
		return a.w<b.w;
	});
	for(int i=1,j=1;i<m||j<mm;){
		if(m<=i){
			e[i+j-1]=pl[j];
			j++;
		}else if(mm<=j){
			e[i+j-1]=backup[i];
			i++;
		}else{
			if(e[i].w<pl[j].w){
				e[i+j-1]=backup[i];
				i++;
			}else{
				e[i+j-1]=pl[j];
				j++;
			}
		}
	}
	mm+=m;
	for(int i=1;i<=n+k;i++){
		f[i]=i;
		size[i]=1;
	}
	for(int i=1;i<=mm;i++){
		int u=e[i].u,v=e[i].v,w=e[i].w;
		u=find(u),v=find(v);
		if(u==v){
			continue;
		}
		cost+=w;
		if(cost>=ans){
			return;
		}
		if(size[u]<size[v]){
			f[u]=v;
			size[v]+=size[u];
		}else{
			f[v]=u;
			size[u]+=size[v];
		}
	}
	ans=min(ans,cost);
}
void dfs(int p){
	if(p>k){
		solve();
		return;
	}
	mode[p]=0;
	dfs(p+1);
	mode[p]=1;
	dfs(p+1);
}
int main(){
	freopen("road.in","r",stdin);
	freopen("road.out","w",stdout);
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	cin>>n>>m>>k;
	for(int i=1;i<=m;i++){
		cin>>e[i].u>>e[i].v>>e[i].w;
	}
	for(int i=1;i<=k;i++){
		cin>>c[i];
		for(int j=1;j<=n;j++){
			cin>>a[i][j];
		}
		if(!c[i]){
			bool flag=false;
			for(int j=1;j<=n;j++){
				if(!a[i][j]){
					flag=true;
					for(int l=1;l<=n;l++){
						if(j==l){
							continue;
						}
						e[++m]={j,l,a[i][l]};
					}
					break;
				}
			}
			if(flag){
				i--,k--;
			}
		}
	}
	sort(e+1,e+m+1,[](edge a,edge b){
		return a.w<b.w;
	});
	for(int i=1;i<=m;i++){
		backup[i]=e[i];
	}
	dfs(1);
	cout<<ans<<'\n';
	
	//~ cerr<<clock()*1.0/CLOCKS_PER_SEC<<"s\n";
	
	cout.flush();
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T3 比较诡异,字符串不是很会,一直在想 T2 也没想什么做法。打个暴力跑路,甚至还研究了一下 string 类的子函数 find() 的用法。

赛时代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
#include<set>
using namespace std;
constexpr const int N=2e5;
int n;
string s[N+1][2];
int main(){
	freopen("replace.in","r",stdin);
	freopen("replace.out","w",stdout);
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	int q;
	cin>>n>>q;
	for(int i=1;i<=n;i++){
		cin>>s[i][1]>>s[i][2];
	}
	while(q--){
		string t1,t2;
		cin>>t1>>t2;
		int ans=0;
		for(int i=1;i<=n;i++){
			int last=0;
			while(true){
				int pl=t1.find(s[i][1],last);
				if(pl==string::npos){
					break;
				}
				last=pl+s[i][1].size();
				if(t1.substr(0,pl)+s[i][2]+t1.substr(last,t1.size()-last)==t2){
					ans++;
				}
			}
		}
		cout<<ans<<'\n';
	}
	
	cout.flush();
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T4 暴力结束。

赛时代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
#include<set>
using namespace std;
constexpr const int N=500,M=500,P=998244353;
int n,m,s[N+1],c[N+1],a[N+1];
int main(){
	freopen("employ.in","r",stdin);
	freopen("employ.out","w",stdout);
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		char ch;
		cin>>ch;
		s[i]=ch-'0';
	}
	for(int i=1;i<=n;i++){
		cin>>c[i];
	}
	for(int i=1;i<=n;i++){
		a[i]=i;
	}
	int ans=0;
	do{
		int success=0;
		for(int i=1,fail=0;i<=n;i++){
			if(fail>=c[a[i]]||!s[i]){
				fail++;
			}else if(s[i]){
				success++;
			}
		}
		if(success>=m){
			ans++;
		}
	}while(next_permutation(a+1,a+n+1));
	cout<<ans<<'\n';
	
	cout.flush();
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}

最终获得了 \(100+72+35+8=\text{215pts}\)

总结与收获

只能说打的奇差无比

今年的 CSP-J 几乎是最简单的,然而我的 T4 却只有 \(\text{80pts}\),这是不合理的。赛时想到完全背包之后还有 \(\text{2h}\),然而却想不到优化方法。无论是正难则反维护小于等于 \(a_i\) 的 DP 状态,还是压缩 DP 状态,均没有想到。之后还是需要继续加强 DP 训练。


而 CSP-S,虽然说前 \(3\) 题较去年难度有所上升,但我也学习了一年,却只得到了 \(\text{215pts}\),这完全不应当。(去年 \(\text{220pts}\)

T1 其实浪费了大量时间,约 \(\text{1h30min}\) 的时候我才写出来,思维上还是需要多训练;尤其要注意题目中的关键词眼(T1 即超过 \(\dfrac n2\) 的部门至多只有一个)。

T2 更不知道在做什么。先是长时间的玄学卡常不去思考正解,认为自己写不出来,同时最后想到了又不去写。不看大样例的范围,大样例没有跑满,认为大样例 \(\text{0.3s}\) 稳过,也没有对拍。赛后就写出来了 \(\mathcal O\left(2^knk\right)\) 正解……

T3、T4 完全是除了打暴力就没什么时间思考了。


也许和一直在学 whk 没怎么训练有关,但也许也没有太大关系。之后 NOIP 肯定是要停课集训的,估计会好一些。

posted @ 2025-11-08 23:41  TH911  阅读(3)  评论(0)    收藏  举报