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 肯定是要停课集训的,估计会好一些。

浙公网安备 33010602011771号