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