CSP 2025 游记

Posted by TH911 on November 8, 2025

CSP-J 2025

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

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

赛时代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include 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; } ``` </details> T2 不想推导,直接模拟秒了。
赛时代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include 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()); 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; } ``` </details> 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)$。
赛时代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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_setcnt; 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; } ``` </details> 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)$。
赛时代码

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

```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include 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; } ``` </details> *** 最终得到了 $100+100+100+80=380\text{pts}$,遗憾离场。 # CSP-S 2025 先读了一下 T1、T2、T3、T4 的题面,感觉 T2 比较好写,先写了个暴力。 之后看 T1,经过一些思考发现至多有 $1$ 个部分会超过一半。那么考虑先将所有的都丢到最优的部门里,如果存在一个部门超过一半,就按照「偏移量」(最大值减去次大值)最小的开始丢到其他部门里,类似于可反悔贪心。
赛时代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; vectors[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]); } } vectorf; 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; } ``` </details> 之后看 T2,发现 $k\leq10$,于是发现复杂度带 $2^k$ 是比较正确的。之后进行了长时间的玄学卡常,最终获得了 $72\text{pts}$。
赛时代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include 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; } ``` </details> T3 比较诡异,字符串不是很会,一直在想 T2 也没想什么做法。打个暴力跑路,甚至还研究了一下 `string` 类的子函数 `find()` 的用法。
赛时代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; } ``` </details> T4 暴力结束。
赛时代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; } ``` </details> 最终获得了 $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 肯定是要停课集训的,估计会好一些。