T1
题目
P14357 CSP-J 2025 拼数

思路
初步思路
把所有的数字拿出来,对数字排序。
代码实现
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
string s;
int a[N];
int main()
{
cin>>s;
int len=s.size();
int cnt=0;
for(int i=0;i<len;i++)
{
if(s[i]>='0'&&s[i]<='9')
{
a[++cnt]=s[i]-'0';
}
}
sort(a+1,a+cnt+1,greater<int>());
for(int i=1;i<=cnt;i++)cout<<a[i];
return 0;
}
从大到小sort(a+1,a+n+1,greater());
优化
比较简单的一道题,稳妥一点可以用计数排序。
计数排序的核心思想是:
统计每个值出现的次数
按顺序输出对应次数的值
因为只有10种数字('0'-'9'),数据范围极小,用计数排序的时间复杂度 O(n),比快速排序的 O(n log n) 更快。
计数排序的具体实现?
原数组: 4, 2, 2, 8, 3, 3, 1
- 统计每个数出现的次数
| 数值 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|
| 次数 | 1 | 2 | 2 | 1 | 0 | 0 | 0 | 1 | 0 |
- 按从小到大输出
1 2 2 3 3 4 8
计数排序代码
cpp
#include<bits/stdc++.h>
using namespace std;
int a[1000];
string b;
int main(){
cin>>b;
for(int i=0;i<b.length();i++){
a[b[i]]++;//统计每个数字的数量
}
for(char i='0';i<='9';i++){
for(int j=1;j<=a[i];j++)cout<<i;
}
return 0;
}
本题目代码实现
cpp
#include<bits/stdc++.h>
using namespace std;
int a[1000];
string b;
int main(){
cin>>b;
for(int i=0;i<b.length();i++){
a[b[i]]++;//统计每个数字的数量
}
for(char i='9';i>='0';i--){
for(int j=1;j<=a[i];j++)cout<<i;
}
return 0;
}
T2
P14358 CSP-J 2025 座位
题目含义
有一组数据,有一个n*m的矩阵
- 把这组数据从小到大排序
- 走蛇形放置在n*m的矩阵中
- 求第一个人的排序后的行和列坐标
求解思路
初步思路
把这个矩阵构建出来。再寻找a1的位置。
代码实现
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=12;
int b[N*N];
struct Node{
int x,y,val,id;
}a[N*N];
bool cmp(Node p,Node q)
{
return p.val>q.val;
}
int main()
{
int n,m;
cin>>n>>m;
int len=n*m;
for(int i=1;i<=len;i++)
{
cin>>b[i];
a[i]={0,0,b[i],i};
}
sort(a+1,a+len+1,cmp);
int cnt=1;
for(int i=1;i<=m;i++)//枚举每一列
{
if(i%2==1)//奇数
{
for(int j=1;j<=n;j++) //每一列要填n个数
{
a[cnt].x=i;
a[cnt].y=j;
cnt++;
}
}
else //偶数
{
for(int j=n;j>=1;j--)
{
a[cnt].x=i;
a[cnt].y=j;
cnt++;
}
}
}
for(int i=1;i<=len;i++)
{
if(a[i].id==1)
{
cout<<a[i].x<<" "<<a[i].y<<" ";
break;
}
}
return 0;
}
优化1
不用把每个人都填进去,只需要找a1的位置就好
cpp
R = a[1];
sort(a + 1, a + n * m + 1, cmp);
for (int i = 1; i <= n * m && a[i] ^ R; i++) { //a[i] ^ R 等于 a[i] != R
//y & 1 就是取 y 的二进制最低位,用于判断奇偶
//奇数列,向下走,x增加,y不变
if (x < n && y & 1) {
x++;
continue;
}
//最后一行,y+1,x不变
if (x == n && y & 1) {
y++;
continue;
}
//偶数列,从下往上,x--,y不变
if (x > 1 && !(y & 1)) {
x--;
continue;
}
//第一行, y+1,处理下一列
if (x == 1 && !(y & 1)) {
y++;
continue;
}
}
用二进制处理
- ai ^ R ,值为1,即不同。
- y&1判断是奇数列还是偶数列
优化2
能不能不模拟,利用数学方法直接计算答案呢?
第一步:先计算它的排序位置p
第二步:计算列
y=(p-1)/n+1
第三步:计算行
- 如果是奇数列(从上往下填)
x = p % n ? p % n : n - 如果是偶数列(从上往下填)
x = n - (p % n ? p % n : n) + 1
cpp
#include<bits/stdc++.h>
using namespace std;
int a[10009];
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
cin >> n >> m;
int len = n * m;
for(int i = 1; i <= len; i++) {
cin >> a[i];
}
// 第一步:计算 a[1] 的排名 p(从大到小)
int p = 1;
for(int i = 2; i <= len; i++) {
if(a[i] > a[1]) {
p++;
}
}
// 第二步:计算列号 y
int y = (p - 1) / n + 1;
// 第三步:计算行号 x
int x;
if(y % 2 == 1) {
x = p % n ? p % n : n;
} else {
x = n - (p % n ? p % n : n) + 1;
}
cout << y << " " << x << endl;
return 0;
}
T3 CSP-J 2025 异或和

题目含义
求解思路一
1.利用前缀和思想,计算区间异或值
- 异或和si的值计算
si=si-1^ai - 区间异或值
l,r之间的异或值为k
sr^sl-1=k
2.如何得到更多的不相交区间,贪心 - 枚举右端点
- 看之前是否有能与该右端点的si异或值为k的相匹配的左端点
即是否有sr^k
这里可以借助unordered_map实现 - 如果有这样的左端点,再看与上一个区间是否重叠
- 如果有这样的左端点,再看与上一个区间是否重叠
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
unordered_map<int,int>mp;
int a[N],s[N];
int main()
{
int n,k,cnt=0;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
s[i]=s[i-1]^a[i];
}
mp[0] = 0;
//枚举右端点
int lat=0;
for(int i=1;i<=n;i++)
{
if(mp.count(s[i]^k) && mp[s[i]^k]>=lat)
{
cnt++;
lat=i;
}
mp[s[i]]=i;
}
cout<<cnt;
return 0;
}
求解思路二
用dp求解
dp定义
dpi = 在前 i 个元素中,最多能选出多少个异或和为 k 的不相交区间。
转移方程
对于位置 i,我们考虑最后一个区间是否以 i 为右端点:
- 不选i:dpi-1,不选以 i 结尾的区间
- 选 i:
有一个区间j,i,异或和为 si ^ sj-1 = k,然后加上前面部分dpj-1的最优解,即
cpp
for (int i=1;i<=n;i++){
for (int j=1;j<=i;j++)
dp[i]=max(dp[i], dp[j-1] + (int)((s[i]^s[j-1])==k));
}
但这个问题是时间复杂度O(n2)
怎么优化
第一个循环无法优化,对第二个循环想办法
这里就是找到一个区间的左端点,对于贪心而言,找到最靠右的左端点肯定是最好的。
结合上一方法,只要判断是否有si^k的左端点存在即可
那么
cpp
#include <bits/stdc++.h>
using namespace std;
int a[500005];
int dp[500005];
int s[500005];
unordered_map<int,int> mp;
int main(){
mp[0]=0;
int n,k;
cin>>n>>k;
for (int i=1;i<=n;i++){
cin>>a[i];
s[i]=a[i]^s[i-1];
}
for (int i=1;i<=n;i++){
//不选i
dp[i]=dp[i-1];
//选i
int cur=k^s[i];
if (mp.count(cur)) dp[i]=max(dp[i],dp[mp[cur]]+1);
mp[s[i]]=i;
}
cout<<dp[n];
}
我的bug
cpp
int main(){
mp[0]=0;
int n,k;
cin>>n>>k;
for (int i=1;i<=n;i++){
cin>>a[i];
s[i]=a[i]^s[i-1];
mp[s[i]]=i;
}
for (int i=1;i<=n;i++){
//不选i
dp[i]=dp[i-1];
//选i
int cur=k^s[i];
if (mp.count(cur)) dp[i]=max(dp[i],dp[mp[cur]]+1);
}
cout<<dp[n];
}
我是把mps\[i]=i;预处理出了,这样可能发生的问题是
mp 里存的是值为si所有位置,包括当前位置 i 和后面的值为si的位置。
要把mps\[i]=i;放在第二个循环中,边用变算,使得si对应的位置都是i之前的。
总结拓展
如果没有思路,看一下特殊性质
特殊性质A
特殊性质A ,所有的ai都是1,这里对应的k=0
那么当区间个数为偶数是,异或值为0,奇数为1.
所以
这是只针对性质A的代码
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
}
if (k == 0) {
cout << n / 2 << endl;
} else if (k == 1) {
cout << n << endl;
} else {
cout << 0 << endl;
}
return 0;
}
特殊性质B
特殊性质 B: 对于所有 1≤i≤n,均有 0≤ai≤1。
即每个数字是0或者1。
-
k=0的情况,即要求序列中1的数量都是偶数,遇到0就直接成组。
-
k=1的情况,即要求序列中1的数量都是奇数,即遇到1就是一个合法区间。
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int a[N];
int main()
{
int n,k;
cin>>n>>k;
int cnt=0,ans=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(k==0)
{
if(a[i]==1) cnt++;
if(cnt%2==0 || a[i]==0)
{
ans++;
cnt=0;
}
// cout<<"!"<<ans<<" ";
}
else
{
if(a[i]==1) ans++;
}
}
cout<<ans;
return 0;
}
以上的代码能将同时通过A、B的测试点。
T4 CSP-J 2025 多边形

题目分析
从 n 根木棍里选若干根,能拼成多边形的方案数。
- 题目条件
原条件:sum > 2 × max
两边同减去max
等价于:sum - max > max
因为题目中说下标没有影响,只考虑集合。
因此可以对所有木棍长度排序。
对于第i根木棍,他前面的长度之和要大于当前木棍的长度。
| 排序的作用 |
|---|
| 因为题目只考虑集合(木棍的编号不影响),所以可以排序。 |
| 排序后,对于第 i 根木棍 ai,它前面的木棍(1 ~ i-1)长度都 ≤ ai,它后面的木棍(i+1 ~ n)长度都 ≥ ai |
| 如果 ai 要成为最长木棍,那么:只能从它前面选木棍,后面的木棍不能选(否则最长木棍就不是 ai 了)。 |
题目转化为
对于第 i 根木棍,只能选它前面的木棍,前面的长度之和 > ai的方案数。
转化为01背包问题
| 01背包 | 本题 |
|---|---|
| 有 m 件物品 | 这里是前 i-1 根木棍 |
| 每件物品有重量 wj | 木棍长度ai |
| 每件物品只能选一次(选或不选) | 木棍只看长度,用和没用 |
| 能凑出重量 ≤ W 的方案数 | i之前选用木棍长度之和>ai的方案数 |
状态定义
dpi从前i-1根木棍中选择长度和>ai的方案数。
- 计算i-1中选木棍的整体方案数,排除不合法的方案数,得到合法结果
不合法的情况
| 不合法方案 | 数量 |
|---|---|
| 选0个 | 1 |
| 选1个 | i-1 |
| 选2个及以上但和 ≤ ai | 用dp计算 |
dpi表示在处理到第 i 根木棍时,它前面所有木棍中,能够凑出长度和 ≤ ai 的方案总数。
- 进一步细化就是,dpi的计算为在前i-1根木棍中,
前i-1根木棍中选使其长度为0的方案+前i-1根木棍中选使其长度为1的方案+前i-1根木棍中选使其长度为2的方案+......+前i-1根木棍中选使其长度为ai的方案
以上这些都是不合法的,需要减去,用代码实现就是
cpp
for(int i=1;i<=n;i++)
{
long long sum=0;
//因为这里计算的是不合法的, 不合法即长度之和<=a[i]
//要计算出长度[0,a[i]]这个范围内的每一种方案数
for(int j=0;j<=a[i];j++)
{
sum=(sum+dp[j])%mod;
}
ans=(ans-sum+mod)%mod;
}
- dpj的值是多少呢?
长度为j的方案数dpj,细分为用不用ai
| 用不用ai | dp\[\] | 解释 |
|---|---|---|
| 不用ai | dpj | 从前面 i-1 根中凑出长度 j 的方案 |
| 用ai | dpj-a\[i] | 前面凑出 j-ai,加上 ai 后总长度为 j |
回顾01背包
因为每根木棍只有两种状态:
选:从 j-ai 转移过来
不选:保持原来的 dpj
01 背包的"选/不选"模型。
cpp
for(int i=1;i<=n;i++)
{
long long sum=0;
//因为这里计算的是不合法的, 不合法即长度之和<=a[i]
//要计算出长度[0,a[i]]这个范围内的每一种方案数
for(int j=0;j<=a[i];j++)
{
sum=(sum+dp[j])%mod;
}
ans=(ans-sum+mod)%mod;
// //前i-1个木棍中选长度为j 和 长度为j-a[i]配合a[i]长度恰好为j的两种方案
for(int j=5000;j>=a[i];j--)
{
dp[j]=(dp[j]+dp[j-a[i]])%mod;
}
}
重点:为何逆序
保证每个木棍只被用一次
dp 中已经包含了所有单根木棍的方案!不用再减去
代码实现
cpp
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
int a[5005],dp[5005];
long long ans;
long long p[5005];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+n+1);
p[0]=1;
for(int i=1;i<=5000;i++)
{
p[i]=p[i-1]*2%mod;
}
ans=(p[n]-1+mod)%mod;
dp[0]=1;
for(int i=1;i<=n;i++)
{
long long sum=0;
//因为这里计算的是不合法的, 不合法即长度之和<=a[i]
//要计算出长度[0,a[i]]这个范围内的每一种方案数
for(int j=0;j<=a[i];j++)
{
sum=(sum+dp[j])%mod;
}
ans=(ans-sum+mod)%mod;
// //前i-1个木棍中选长度为j 和 长度为j-a[i]配合a[i]长度恰好为j的两种方案
for(int j=5000;j>=a[i];j--)
{
dp[j]=(dp[j]+dp[j-a[i]])%mod;
}
}
cout<<ans;
return 0;
}