2025~2026赛季 正赛部分题解
chenzefan
·
·
科技·工程
:::::success[\text{CSP-J 2025}]
温馨提醒:由于 T1 和 T2 过于简单,暂不提供部分分解法。
::::info[T1]
把字符串 $S$ 内属于 `0`$\sim$`9` 的字符加入大根堆(或其他 $\text{STL}$ 容器),自动降序后输出。瓶颈在 $\text{STL}$ 容器内部排序处。
$O(n)$ 做法:
直接开一个大小为 $10$ 的数组 $a$,$a_i$ 表示字符串 $S$ 中字符 `i+'0'` 出现的次数。故可以不排序,枚举 $i=9\sim0$ 输出 $a_i$ 个对应数字即可。
:::info[T1 [$\text{AC}$](https://www.luogu.com.cn/record/244739740) 代码]
```cpp
#include<bits/stdc++.h>
using namespace std;
string s;
priority_queue <char> q;
signed main(){
cin>>s;
for(int i=0;i<s.size();i++)
if(s[i]>='0'&&s[i]<='9') q.push(s[i]);
while(!q.empty()) cout<<q.top(),q.pop();
return 0;
}
```
:::
::::
::::info[[T2](https://www.luogu.com.cn/problem/P14358)]
#### 线性找排名:找出 $a_1$ 的排名。
$O((nm)\log(nm))$ 做法:
对序列 $a$ 排序,由于有 $1\le n,m \le 10$,则 $1\le nm \le 100$ 轻松通过。
$O(nm)$ 做法:
输入时直接判断 $a_i$ 是否大于 $a_1$,满足条件则计数,即表示为 $a_1$ 后移一位。
#### 蛇形找位:得出答案。
$O(nm)$ 做法:
由于 $1\le nm \le 100$,直接模拟即可通过,即枚举次数同时朝对应方向移动,到达边界则判断。
$O(1)$ 做法:
推式子。我们发现每 $2n$ 个会形成周期,则可以将排名对 $2n$ 取模进行判断。
注意输出的是“第几列第几行”。
:::info[T2 [$\text{AC}$](https://www.luogu.com.cn/record/244739765) 代码]
```cpp
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n,m,a[N],cnt;
signed main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n*m;i++){
scanf("%d",a+i);
if(a[i]>a[1]) cnt++;
}
int x=1,y=1,d=1;
while(cnt--){
x+=d;
if(x>n) x=n,y++,d=-1;
if(x<1) x=1,y++,d=1;
}printf("%d %d",y,x);
return 0;
}
```
:::
::::
::::info[[T3](https://www.luogu.com.cn/problem/P14359)]
不提供特殊性质做法。
$O(n^3)$ 做法:
直接 $\text{dp}$,定义 $dp_i$ 表示前 $i$ 个所能分割出的最多区间。则不难有 $dp_i=\displaystyle\max_{j=1}^i[\displaystyle\bigoplus_{l=j}^i a_l=k]\times(dp_{j-1}+1)$。其中 $[]$ 内判断若满足则为 $1$,否则为 $0$。则枚举 $i$ 为 $O(n)$,枚举 $j$ 为 $O(n)$,枚举 $l$ 为 $O(n)$,总时间复杂度为 $O(n^3)$。
期望得分:$40$ 分。
$O(n^2)$ 做法:
优化算法一的时间复杂度。注意到异或和有如下性质:
$$
a \oplus a =0
$$
即相同两数异或为 $0$。则可以对 $l$ 的循环优化为 $O(1)$。预处理出前缀异或和 $xor_i=xor_{i-1} \oplus a_i$。则有 $\displaystyle\bigoplus_{i=l}^r a_i=xor_r \oplus xor_{l-1}$。
所以之间复杂度优化成了 $O(n^2)$。
期望得分:$60$ 分。
$O(n) /O(n \log n)$ 做法:
考虑贪心。要使分割序列越长,每段肯定越短越好。则我们要对每个 $r$ 找到最近的 $l$ 使得 $xor_r \oplus xor_{l-1}=k$。
记变量 $last$ 表示区间 $[1,last]$ 已经不能再分割了。
若上式满足 $l>last$,则显然可以分割,贪心的考虑移动: $last=r$。即 $r$ 以及之前的都不能再使用了。
考虑如何 $O(1)$ 求 $l$。显然对于 $xor_r \oplus xor_{l-1}=k$ 可以移项 $xor_r \oplus k=xor_{l-1}$。由于左式可以 $O(1)$ 算,只要知道满足条件的 $xor_{l-1}$ 在此之前是否存在过就可以了。用 $\text{map}$ 记录是 $O(n \log n)$ 的,而改用数组则为 $O(n)$。
期望得分:$100$ 分。
:::info[T3 [$\text{AC}$](https://www.luogu.com.cn/record/244824991) 代码]
```cpp
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
const int M=3e6+5;
int n,k,a[N],sum_xor[N],ans,last,id[M];
signed main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",a+i),sum_xor[i]=sum_xor[i-1]^a[i];
memset(id,-0x3f,sizeof(id));id[0]=0;
for(int r=1;r<=n;r++){
int x=sum_xor[r]^k;
if(id[x]>=last) ans++,last=r;
id[sum_xor[r]]=r;
}
printf("%d",ans);
return 0;
}
```
:::
::::
::::info[[T4](https://www.luogu.com.cn/problem/P14360)]
$O(2^n)$ 做法:
枚举木棒子序列,同时维护和 $\operatorname{sum}$ 以及最大值 $\max$,再用 $O(1)$ 判断能否成功即可。直接暴力 $\text{dfs}$。
期望得分:$40$ 分。
当 $\displaystyle\max_{i=1}^n a_i \le 1$ 时:
通过上述暴力程序可以得出下表(满足 $\forall i \in [1,n] a_i=1$):
| $n$ | $ans$ |
|:-:|:-:|
| $3$ | $1$ |
| $4$ | $5$ |
| $5$ | $16$ |
| $6$ | $42$ |
| $7$ | $99$ |
| $\cdots$ | $\cdots$ |
有如下结论:$ans=\displaystyle\sum_{i=3}^n \text{C}_n^i$。
有根据公式 $\text{C}_n^m=\frac{n!}{m!(n-m)!}$。
所以预处理出 $i!(i \in [1,n])$,然后费马小定理求逆元即可。
时间复杂度为 $O(n \log n)$,求线性逆元可以做到 $O(n)$。
期望得分:$24$ 分。
$O(n^2)$ 做法:
我们发现题目要求的是子序列而非字串。所以 $a_i$ 之间顺序无影响。考虑对 $a$ 升序排序。
所以因为 $\forall i\in[1,n) a_i<a_{i+1}$,则有 $\forall i\in[1,m) l_i<l_{i+1}$,这样就可以将题目给的式子 $\displaystyle\sum_{i=1}^m l_i>2 \times \displaystyle\max_{i=1}^m l_i$ 转化成了 $\displaystyle\sum_{i=1}^{m-1} l_i>l_m$。
枚举 $a_k=\displaystyle\max_{i=1}^k a_i(k\in [3,n])$,我们可以将题目的限制转化为:
> 从 $a$ 的 $[1,k−1]$ 中选木棍,使其长度和 $>a_k$ 的方案数。
联想到了 $\text{dp}$。设计状态 $dp_{i,j}$ 表示考虑前 $i$ 个木棍,长度和 $>j$ 的方案数。
然后发现这样搞很难。于是换一下:
> 设计状态 $dp_{i,j}$ 表示考虑前 $i$ 个木棍,长度和 $=j$ 的方案数。
然后容斥原理搞一搞就完事了。
状态转移:$dp_{i,j}=
\begin{cases}
dp_{i-1,j} &j<a_i\\
dp_{i-1,j}+dp_{i-1,j-a_i} &j \ge a_i
\end{cases}
然后容易得多了。初始化:dp_{i,0}=1(i \in [1,n])。
最后用总方案数减去算出来不满足条件的方案数即可。
期望得分:100 分。
:::info[T4 \text{AC} 代码]
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5005;
const int mod=998244353;
int n,a[N],dp[N],sum[N],ans;
int qpow(int a,int b){
int ans=1;
while(b){
if(b&1) ans=ans*a%mod;
a=a*a%mod,b>>=1;
}return ans;
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",a+i);
sort(a+1,a+n+1);
dp[0]=dp[a[1]]=1;
for(int i=2;i<=n;i++){
for(int j=N-5;j>=a[i];j--) dp[j]=(dp[j]+dp[j-a[i]])%mod;
for(int j=0;j<=a[i+1];j++) sum[i]=(sum[i]+dp[j])%mod;
}
for(int i=3;i<=n;i++) ans=(ans+(qpow(2,i-1)-sum[i-1]+mod)%mod)%mod;
printf("%lld",ans);
return 0;
}
:::
::::
:::::
:::::success[\text{CSP-S 2025}]
温馨提醒:由于 T3 和 T4 过于困难,暂不提供解法。
::::info[T1]
并没有想到部分分做法。
首先看到题目就是一眼**贪心**。观察第 $3$ 个样例,思路很明显了,我们需要先把每个人好感度最大值相加,找到最优方案(不应顶满足条件),然后我们开始**反悔**。
本题要求每个社团人数不超过 $\frac{n}{2}$,又因为 $n$ 为偶数。所以不满足条件的社团只有一个。所以单一考虑即可。
分类讨论此时哪个社团人数超过了 $\frac{n}{2}$,我们要考虑将其中一部分人**转移**到另一个社团去。又因为我们想要好感度和最大,那么转移的差就要最小,不妨用 $\text{STL}$ 容器排序即可。
期望得分:$100$ 分。
:::info[T1 [$\text{AC}$](https://www.luogu.com.cn/record/249044047) 代码]
```cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int t,n,cnt1,cnt2,cnt3,ans,id[N];
struct node{int x,y,z;}a[N];
vector <int> vec;
signed main(){
scanf("%d",&t);
while(t--){
scanf("%d",&n);ans=cnt1=cnt2=cnt3=0;
int m=n/2;vec.clear();
for(int i=1;i<=n;i++){
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
int maxx=max({a[i].x,a[i].y,a[i].z});ans+=maxx;
if(maxx==a[i].x) cnt1++,id[i]=1;
else if(maxx==a[i].y) cnt2++,id[i]=2;
else cnt3++,id[i]=3;
}
if(cnt1<=m&&cnt2<=m&&cnt3<=m){printf("%d\n",ans);continue;}
for(int i=1;i<=n;i++){
if(cnt1>m&&id[i]==1) vec.push_back(a[i].x-max(a[i].y,a[i].z));
if(cnt2>m&&id[i]==2) vec.push_back(a[i].y-max(a[i].x,a[i].z));
if(cnt3>m&&id[i]==3) vec.push_back(a[i].z-max(a[i].x,a[i].y));
}sort(vec.begin(),vec.end());
int i=0,cnt=max({cnt1,cnt2,cnt3});
while(cnt>m) ans-=vec[i++],cnt--;
printf("%d\n",ans);
}
return 0;
}
```
:::
::::
::::info[[T2](https://www.luogu.com.cn/problem/P14362)]
$O(2^k(kn^2+(kn^2+m)\log(kn^2+m)))$ 做法:
对于 $2^k$ 种乡村选择全部枚举一边,对于每个乡村与原城市的边权进行建边,即设加入了乡村 $i$,则建 $(j,k,a_{i,j}+a_{i,k})$ 共 $n^2$ 条边。再跑最小生成树 $\text{kruskal}$ 算法即可。
期望得分:$16$ 分(即 $k=0$ 的数据)。
$O(2^k(kn+(kn+m)\log(kn+m)))$ 做法:
在上一个算法中加入乡村虚点,则建边就是 $kn$ 的,同样跑 $\text{kruskal}$。
期望得分:$32$ 分。
$O(2^k(kn+(kn)\log(kn))+m\log m)$ 做法:
我们发现原城市的 $m$ 条边每次都参与了排序,很耗时间,考虑只做一次。则提前预处理排序,做 $\text{kruskal}$ 在新边和旧边中找边权小的即可。
期望得分:$80$ 分。
$O(2^k(kn+m)+(kn+m)\log(kn+m))$ 做法:
因为所有边一开始已经确定,不妨将所有边一并存下来排好序,同时新增一个 $id$ 变量,表示是从哪个乡村来拿出来的边。这样在跑 $\text{kruskal}$ 时看看这条边对应的乡村有没有选即可。
期望得分: $100$ 分。
拓展:$O(2^k(kn+n)+(kn+n)\log(kn+n)+m\log m)$ 做法:
由于 $1\le m \le 10^6$,赛场上不确定上个做法会不会错。因为一开始的 $m$ 条边中只有 $n-1$ 条边有用,即原图最小生成树上的边,则我们可以将前 $m$ 条边线做 $\text{kruskal}$ 变成 $n-1$ 条边,即从 $10^6$ 级瞬间变成了 $10^4$ 级。即可优化原算法。
期望得分:$100$ 分。
时间对比。[$13.02 \text s$](https://www.luogu.com.cn/record/249048123) 和 [$8.27 \text s$](https://www.luogu.com.cn/record/249048972)。
:::info[T2 [$\text{AC}$](https://www.luogu.com.cn/record/249048972) 代码]
```cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e4+5;
const int M=1e6+5;
const int K=15;
int n,m,k,fa[N+K],c[K],a[K][N],cnt,ans,ans1=LLONG_MAX;
struct node{int u,v,w,id;}e1[M],e[M+N*K];
bool cmp(node x,node y){return x.w<y.w;}
bool vis[K];
int find(int x){
if(x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}
void unionn(int x,int y,int w){
x=find(x),y=find(y);
if(x!=y) fa[y]=x,cnt++,ans+=w;
}
void kruskal(int t){
cnt=0;
for(int i=1;i<=n+k;i++) fa[i]=i;
for(int i=1;i<=m;i++){
int u=e[i].u,v=e[i].v,w=e[i].w,id=e[i].id;
if(!vis[id]&&id) continue;
unionn(u,v,w);
if(cnt==n+t-1) break;
}
}
void work(){
int tot=0;
for(int i=1;i<=k;i++) tot+=(vis[i]!=0);
kruskal(tot);ans1=min(ans1,ans);
}
void dfs(int i,int sum){
if(i==k+1){
ans=sum;work();
return ;
}
vis[i]=1;dfs(i+1,sum+c[i]);
vis[i]=0;dfs(i+1,sum);
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1,u,v,w;i<=m;i++) scanf("%lld%lld%lld",&u,&v,&w),e1[i]={u,v,w,0};
for(int i=1;i<=n;i++) fa[i]=i;sort(e1+1,e1+m+1,cmp);
for(int i=1;i<=m;i++){
int u=e1[i].u,v=e1[i].v,w=e1[i].w,last=cnt;
unionn(u,v,w);
if(last==cnt-1) e[cnt]={u,v,w,0};
if(cnt==n-1) break;
}m=n-1;
for(int i=1;i<=k;i++){
scanf("%lld",c+i);
for(int j=1;j<=n;j++) scanf("%lld",&a[i][j]),e[++m]={n+i,j,a[i][j],i};
}
sort(e+1,e+m+1,cmp);
if(k==0) return printf("%lld",ans),0;
dfs(1,0);printf("%lld",ans1);
return 0;
}
```
:::
::::
:::::
:::::success[$\text{NOIP 2025}$]
**温馨提醒:暂时只有 T1 做法。**
::::info[[T1](https://www.luogu.com.cn/problem/P14635)]
~~感觉部分分就是**背包**,但由于过于简单,我们直接说正解。~~
$O(n \log n + n \log m)$ 做法:
我们发现题目购买的方案似乎和奇偶性有关。考虑分类讨论:
对于当前第 $i$ 种糖果:
- 若购买奇数个:价格为 $k \times (x_i+y_i)+x_i$。$k$ 为非负整数,易得,此时购买了第 $i$ 种糖果 $2k+1$ 个。
- 若购买偶数个(包括**不买**):价格为 $k \times (x_i+y_i)$。$k$ 为非负整数,易得,此时购买了第 $i$ 种糖果 $2k$ 个。
则我们知道,对于每种糖果 $i$,$x_i+y_i$ 可以无限的买,而 $x_i$ 只能买最多一个(因为再多就归到前面一类去了)。
我们令 $X_i=x_i+y_i,Y_i=x_i$。表示两种购买方式。
易得贪心策略,若要买 $k$ 组 $X$ 类型,则一定买 $k$ 组 $(x_i+y_i)_{\min}$。若要买 $Y$ 类型,则选择 $x_i$ 最小且未被选择的。
所以贡献仅有 $(x_i+y_i)_{\min}$ 和 $x_i$ 产生。
故考虑将 $X_i$ 和 $Y_i$ 排序。那么答案为若干组 $X_1$ 和 $Y$ 的任意前缀和构成。
则定义 $sum_i$ 表示 $Y$ 的前缀和。
因为此时花钱越多买的越多,则不妨对购买总数二分答案,对于当前的 $mid$,计算可能的 $k$。
$k$ 是有范围限制的,因为此时购买 $k$ 组 $X$ 和 $mid-2k$ 个 $Y$ 的最小钱数:
$$
now=kX_1+sum_{mid-2k}
$$
显然 $0 \le mid-2k \le n$,可以解得 $\frac{mid-n}{2}\le k\le \frac{mid}{2}$。注意我们枚举得时候,左边界要向上取整,因为再往下一点我们是取不到的。右边界无碍。中途计算会爆 long long,注意开 __int128。这样即可做到 $O(n \log n + n \log m)$。
期望得分:$100$ 分。
$O(n \log n)$ 做法:
尝试不用二分,我们发现切入口可以不是 $X$ 而是 $Y$,排完序后枚举 $Y$ 得购买数量,然后去尽可能地买 $X$,计算最大能买数量,这样排序后时间复杂度从 $O(n \log m)$ 降到了 $O(n)$。总复杂度瓶颈在排序地 $O(n \log n)$。
期望得分:$100$ 分。
:::info[T1 [$\text{AC}$](https://www.luogu.com.cn/record/251540918) 代码]
```cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
int n,m,x[N],y[N],sum[N],ans;
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld%lld",x+i,y+i),y[i]+=x[i];
sort(x+1,x+n+1);sort(y+1,y+n+1);
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+x[i];
for(int i=0;i<=n;i++){
if(m<sum[i]) break;
ans=max(ans,i+(m-sum[i])/y[1]*2);
}
printf("%lld",ans);
return 0;
}
```
:::
::::
:::::
:::::success[$\text{WC 2026}$]
温馨提示:由于太难了,暂时没有解法。
:::::
:::::success[联合省选 $2026$]
温馨提示:由于太难了,暂时没有解法。
:::::
:::::success[$\text{APIO 2026}$]
温馨提示:由于太难了,暂时没有解法。
:::::