2022-2024年睿抗CAIP编程技能赛-本科组(国赛)模拟训练题解
题目难度:2022>2024>2023 (个人观点,仅供参考)
感觉这几年的形式大概是开头1道字符串处理/模拟题,然后一道暴力枚举,一道数据结构,一道图论带DP,剩下一道不固定,不排除又来一个dp或者比较复杂的模拟题......
2024年
RC-u1 大家一起查作弊(分数 15)

输入样例:
点击查看代码
static void nnmMNBkf3kfa(){
    int fefvB4=2;
    int [][]fsdk9A=new int[fefvB4][fefvB4];
    fsdk9A[0][0]=1;
    for (int gfdgsUB3 = 0; gfdgsUB3 < fefvB4; gfdgsUB3++) {
        for (int fdnbXZ8 = 0; fdnbXZ8<fefvB4-gfdgsUB3-1; fdnbXZ8++) {
            fsdk9A[gfdgsUB3][fdnbXZ8+1]=fsdk9A[gfdgsUB3][fdnbXZ8]+gfdgsUB3+fdnbXZ8+2;
            fsdk9A[gfdgsUB3+1][fdnbXZ8]=fsdk9A[gfdgsUB3][fdnbXZ8]+gfdgsUB3+fdnbXZ8+1;
            break;
        }
        break;
    }
}
输出样例:
点击查看代码
155
276 54
思路(字符串处理)
对输入字符串逐字符扫描提取字母数字组成的关键词,并判断其字符类型组合计算可疑分数,再汇总所有关键词的可疑分数、总长度和数量即可。
C++代码实现
点击查看代码
#include <bits/stdc++.h>
#define endl "\n"
using namespace std;
void solve(){
    int res=0,sum=0,num=0;  // res: 可疑分数总和, sum: 关键词总长度, num: 关键词数量
    string s;
    while(cin>>s){  // 按单词读取输入
        int idx=0;
        bool f1=0,f2=0,f3=0;  // f1: 是否包含数字, f2: 是否包含小写字母, f3: 是否包含大写字母
        int len=0;  // 当前关键词长度
        while(idx<(int)s.length()){
            if(s[idx]>='0'&&s[idx]<='9'){
                f1=1;
                len++;
            }else if(s[idx]>='a'&&s[idx]<='z'){
                f2=1;
                len++;
            }else if(s[idx]>='A'&&s[idx]<='Z'){
                f3=1;
                len++;
            }else {  // 遇到非字母数字字符,结束当前关键词
                if(len){  // 如果关键词长度大于0,计算分数并更新统计
                    if(f1&&f2&&f3)res+=5;  // 同时包含三种字符类型
                    else if((f2||f3)&&f1)res+=3;  // 包含数字和其他至少一种类型
                    else if(f2&&f3)res+=1;  // 同时包含大小写字母
                    num++;
                    sum+=len;
                    len=f1=f2=f3=0;  // 重置状态,准备下一个关键词
                }else {
                    len=f1=f2=f3=0;  // 重置状态
                }
            }
            idx++;
        }
        // 处理行末可能的关键词
        if(len){
            if(f1&&f2&&f3)res+=5;
            else if((f2||f3)&&f1)res+=3;
            else if(f2&&f3)res+=1;
            num++;
            sum+=len;
            len=f1=f2=f3=0;
        }
    }
    cout<<res<<endl;
    cout<<sum<<" "<<num<<endl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);
    int _=1;
    // cin>>_;
    while(_--) solve();
    return 0;
}
RC-u2 谁进线下了?II (分数 20)

输入样例:
点击查看代码
3
1 1
2 2
9 3
6 4
7 5
11 6
3 7
13 8
8 9
16 10
4 11
19 12
17 13
5 14
12 15
15 16
14 17
10 18
20 19
18 20
5 11
10 12
30 13
22 14
1 1
28 20
21 16
26 17
2 2
24 3
4 4
29 5
8 6
7 15
6 7
3 8
9 9
25 10
23 19
27 18
19 20
26 19
27 18
18 17
21 16
12 15
28 14
20 13
17 12
14 11
13 10
23 9
29 8
22 7
30 6
15 5
24 4
25 3
16 2
11 1
输出样例:
点击查看代码
1 50
2 42
11 39
24 34
16 31
6 29
9 29
25 28
29 27
3 25
4 25
8 25
13 22
30 21
7 20
15 19
22 19
5 15
17 15
14 12
23 12
10 10
12 10
19 8
20 8
21 8
28 6
26 4
27 4
18 3
思路(排序)
利用数组num快速映射排名对应的分数,用数组存储各队伍总分并初始化为-1,累加各队排名对应分数后,对总分非-1的队伍按总分降序、编号升序排序输出即可。
C++代码实现
点击查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define fi first
#define se second
#define endl "\n"
using namespace std;
// 定义排名与分数的映射数组:num[p]表示第p名对应的分数(p从1到20)
int num[30] = {0,25,21,18,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0};
PII res[31];  // 存储30支队伍的信息(索引1-30对应队伍编号1-30)
// 排序比较函数:先按分数降序,分数相同则按队伍编号升序
bool cmp(PII a, PII b){
    if(a.se != b.se) return a.se > b.se; 
    return a.fi < b.fi;
}
void solve(){
    int n;
    cin >> n;
    // 初始化队伍信息:编号1-30,初始分数设为-1(表示未参赛)
    for(int i=1; i<=30; i++){
        res[i] = {i, -1};
    }
    while(n--){
        for(int i=1; i<=20; i++){
            int c, p;  // c:队伍编号,p:排名
            cin >> c >> p;
            // 计算该队伍在本轮的得分(通过num数组获取)
            int score = num[p];
            // 更新总分:若未参赛过(分数为-1),直接赋值;否则累加
            if(res[c].se == -1){
                res[c].se = score;
            } else {
                res[c].se += score;
            }
        }
    }
    sort(res + 1, res + 31, cmp);
    
    for(int i=1; i<=30; i++){
        if(res[i].se == -1){
            break;  // 未参赛队伍排在后面,遇到第一个未参赛的即可停止
        }
        cout << res[i].fi << " " << res[i].se << endl;
    }
}
signed main(){
    // 关闭输入输出同步,加速cin/cout(在大量输入时有效)
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    
    int _ = 1;
    // cin >> _; 
    while(_--){
        solve();
    }
    
    return 0;
}
RC-u3 势均力敌(分数 25)

输入样例:
点击查看代码
3
5 2 1
输出样例:
点击查看代码
125
512
251
思路(dfs+剪枝)
本题要求将由n(3≤n≤4)个不同个位数字组成的所有n!个n位数分为两组,两组的数字个数均为(n!)/2且平方和相等,最终输出其中一组。
核心思路分为三步:
1. 生成所有可能的 n 位数
通过dfs生成由输入数字组成的所有全排列(即所有可能的n位数)
2. 计算总平方和
遍历aa数组,计算所有n!个数字的平方和,记为maxv。由于题目保证可分为两组平方和相等的集合,因此每组的平方和应为maxv/2,且每组包含n!/2个数字。
3. 查找符合条件的子集
通过dfs筛选出(n!)/2个数字,使得它们的平方和等于maxv/2。搜索过程中通过控制起始索引(避免重复选择)和剪枝(确保剩余数字足够组成目标数量)提高效率,找到符合条件的子集后立即输出。
C++代码实现
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int N=5e6+10,M=1010,mod=1e9+7,INF=0x3f3f3f3f;
int n;  // 输入的n(3或4)
int a[5];  // 存储输入的n个数字
int tt[N];  // 临时数组,在dfs1中存当前排列,在第二个dfs中存选中的数字
int st[N];  // 标记数组,在dfs1中标记数字是否已被使用(1表示已用,0表示未用)
int aa[N];  // 存储所有生成的n位数(全排列结果)
int idx=0;  // 记录aa数组中元素的个数(即n!)
int maxv=0;  // 所有n位数的平方和总和
int flag=0;  // 标记是否找到符合条件的子集(1表示找到)
// 生成所有n位数的全排列,存入aa数组
void dfs1(int x){  // x表示当前要确定第x位数字(1-based)
    if(x>n){  // 若已确定n位数字,组成完整的n位数
        int tmp=0;
        for(int i=1;i<=n;i++){
            tmp=tmp*10+tt[i];  // 将tt中存储的数字组合成n位数(如tt[1]=1,tt[2]=2,tt[3]=5→125)
        }
        aa[idx++]=tmp;  // 存入aa数组,索引递增
        return ;
    }
    // 尝试选择未使用的数字放在第x位
    for(int i=1;i<=n;i++){
        if(st[i]==0){  // 若第i个数字未被使用
            st[i]=1;  // 标记为已使用
            tt[x]=a[i];  // 将第i个数字放在第x位
            dfs1(x+1);  // 递归确定下一位
            st[i]=0;  // 回溯:恢复未使用状态,尝试其他数字
        }
    }
}
// 从aa数组中选idx/2个数字,使其平方和为maxv/2
// x:当前已选数字的个数;h:从aa[h]开始选择(避免重复子集);sum:当前选中数字的平方和
void dfs(int x,int h,int sum){
    // 剪枝:若当前平方和的2倍已超过maxv,或已找到解,直接返回
    if(sum*2>maxv||flag)return;
    // 若已选够idx/2个数字,且平方和为maxv/2(sum*2==maxv)
    if(x==idx/2){
        if(sum*2==maxv){
            // 输出选中的数字
            for(int i=0;i<x;i++)cout<<tt[i]<<endl;
            flag=1;  // 标记已找到解
        }
        return;
    }
    // 从h开始遍历aa数组,选择下一个数字
    for(int i=h;i<idx;i++){
        // 剪枝:确保剩余数字足够选到idx/2个(x + 剩余可选数量 >= idx/2 → x + (idx - i) >= idx/2 → 两边乘2得(x+idx-i)*2 >= idx)
        if((x+idx-i)*2>=idx){
            tt[x]=aa[i];  // 选中aa[i]
            // 递归:已选数量+1,下一次从i+1开始选(避免重复),平方和累加aa[i]^2
            dfs(x+1,i+1,sum+tt[x]*tt[x]);
        }
        if(flag)return;  // 若已找到解,直接退出
    }
}
void solve(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    dfs1(1);  // 生成所有n位数,存入aa数组
    
    // 计算所有n位数的平方和总和maxv
    for(int i=0;i<idx;i++){
        maxv+=aa[i]*aa[i];
    }
    
    // 查找符合条件的子集并输出
    dfs(0,0,0);
}
signed main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    int _ = 1;
    //cin >> _;
    while (_--) solve();
    return 0;
}
RC-u4 City 不 City (分数 30)

输入样例1:
点击查看代码
8 14 7 8
100 20 30 10 50 80 100 100
7 1 1
7 2 2
7 3 1
7 4 2
1 2 1
1 5 2
2 5 1
3 4 1
3 5 3
3 6 2
4 6 1
5 6 1
5 8 1
6 8 2
输出样例1:
点击查看代码
4 50
输入样例2:
点击查看代码
3 1 1 2
10 20 30
1 3 1
输出样例2:
点击查看代码
Impossible
思路(dijkstra+dp)
本题需找到从起点s到终点t的最优路线,核心目标是:
1:总花销最小;
2:若存在多条最小花销路线,选择途经城镇(除s和t外)的最高旅游热度最小的路线。
为了处理旅游热度,我们需要设计一个dp处理:
一.dp[i]表示:从s到i的所有最小花销路径中,途经城镇(不含s和t)的最高旅游热度的最小值。
二. 状态转移方程:
对当前节点x及邻接节点j(边花销cost):
1.路径更短:
 j是终点t:dp[j] = dp[x](t不算途经城镇)。
 j是途经城镇:dp[j] = max(dp[x], w[j])(纳入j的热度)。
2.路径等长:
 j是终点t:dp[j] = min(dp[j], dp[x])(取更小前序热度)。
 j是途经城镇:dp[j] = min(dp[j], max(dp[x], w[j]))(取更小最高热度)。
C++代码实现
点击查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define int long long
#define fi first 
#define se second
#define endl "\n"
using namespace std;
const int N=1e5+10, INF=0x3f3f3f3f3f3f3f3f;
int n,m,s,t;  // n:城镇数;m:通路数;s:起点;t:终点
int h[N],e[N],ne[N],vv[N],idx;  // vv[i]为边的花销
int w[N];  // w[i]为城镇i的旅游热度
int dist[N];  // dist[i]为从s到i的最小花销
int dp[N];  // dp[i]:s到i的最小花销路径中,途经城镇的最高热度的最小值
//int path[N];//记录路径,可省略
void add(int a,int b,int c){
    vv[idx] = c,e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
void dijk(){
    memset(dist, 0x3f, sizeof dist);
    priority_queue<PII, vector<PII>, greater<PII>> pq;
    dist[s] = 0;  // 起点s到自身的距离为0
    dp[s] = 0;    // 起点s无途经城镇,初始最高热度为0
    pq.push({0, s});
    while(pq.size()){
        int x = pq.top().se;
        pq.pop();
        for(int i = h[x]; ~i; i = ne[i]){ 
            int j = e[i];
            int cost = vv[i];  // x到j的花销
            // 情况1:发现一条到j的更短路径(总花销更小)
            if(dist[j] > dist[x] + cost){
                dist[j] = dist[x] + cost;
                //path[j]=x;
                if(j != t){  // j不是终点t,属于途经城镇
                    //当前最高热度是前序最高(dp[x])和j的热度(w[j])的较大者
                    dp[j] = max(w[j], dp[x]);
                } else {  // j是终点t,不算途经城镇,最高热度与前序一致
                    dp[j] = dp[x];
                }
                pq.push({dist[j], j}); 
            }
            // 情况2:路径花销相同,需优化最高热度
            else if(dist[j]==dist[x]+vv[i]){
                //path[j]=x
                if(j!=t)dp[j]=min(max(dp[x],w[j]),dp[j]);
                else dp[j]=min(dp[j],dp[x]);
				//pq.push({dist[j],j});
            }
        }
    }
}
void solve(){
    cin >> n >> m >> s >> t;
    memset(h, -1, sizeof h);
    for(int i = 1; i <= n; i++){
        cin >> w[i];
    }
    while(m--){
        int u, v, k;
        cin >> u >> v >> k;
        add(u, v, k),add(v, u, k); 
    }
    dijk();
    if(dist[t] == INF){ 
        cout << "Impossible" << endl;
    } else {
        cout << dist[t] << " " << dp[t] << endl;
    }
}
signed main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    int _ = 1;
    // cin >> _;
    while(_--) solve();
    return 0;
}
RC-u5 贪心消消乐(分数 30)

输入样例:
点击查看代码
4
0 2 5 0
9 2 -6 2
-4 1 -4 3
-1 8 0 -2
输出样例:
点击查看代码
(1, 2) (2, 4) 15
(3, 1) (3, 1) 5
(4, 2) (4, 3) 5
(2, 4) (2, 4) 2
27

思路(模拟、前缀和、Kadane's算法[最大子矩形之和问题])
一、步骤:
1.找到和最大的子矩形(不含 0,0 标记为负无穷),若有多个和相等的矩形,选择字典序最小的(左列→上行→右列→下行);
2.消除该矩形(置为 0),剩余元素下落(非 0 元素下沉,填补空白);
3.重复至无有效矩形,输出总分数。
二、对于寻找最大子矩形:
暴力解法: 直接通过四重循环枚举所有可能的矩形(左列、右列、上行、下行),计算每个矩形的和并比较。时间复杂度为 O (n⁴)
Kadane's算法: 将二维问题转化为一维问题。固定上下行(或左右列),计算每列(或每行)在该范围内的和,再用 Kadane 算法找最大子数组(对应最优左右列或上下行)。时间复杂度优化为 O (\(n^3\))Kadane 算法 二维 详解[转载]
注意:
1.输入是按先列后行(j,i)输入的,而不是按先行后列(i,j)!
2.模拟掉落的时候冰块也要掉下去,否则只有21分!!!
C++代码实现
24分暴力代码:O(\(n^4\))
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define LL long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
using namespace std;
int n,g[110][110];  // g[列][行]存图
int sumg[110][110]; // 前缀和数组加速
void solve() {
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            cin>>g[j][i];
            if(g[j][i]==0)g[j][i]=-1e10;  // 黑洞标记为负无穷,不会选到,简化逻辑
        }
    int res=0;
    // 循环寻找并消除最大矩形,直到无有效矩形
    while(1){
        // 计算前缀和
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                sumg[i][j]=g[i][j]+sumg[i-1][j]+sumg[i][j-1]-sumg[i-1][j-1];
        
        PII l={-1,-1},r={-1,-1};  // l:矩形左上角(左列, 上行);r:矩形右下角(右列, 下行)
        int maxv=0;  // 最大矩形和
        
        // 四重循环暴力枚举所有可能的矩形
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                for(int x=i;x<=n;x++) 
                    for(int y=j;y<=n;y++){
                        
                        int score=sumg[x][y]-sumg[i-1][y]-sumg[x][j-1]+sumg[i-1][j-1];
                        if(score<=0)continue;
                        
                        if(score>maxv)
                        {
                            maxv=score;
                            l={i,j},r={x,y};
                        }
                        // 若和相等,按字典序选择更小的矩形(左列→上行→右列→下行)
                        else if(score==maxv)
                        {
                            // 字典序比较逻辑:依次比较左列、上行、右列、下行
                            if(l.fi!=i){
                                if(l.fi>i)l={i,j},r={x,y};  // 左列更小,优先选择
                            }else if(l.se!=j){
                                if(l.se>j)l={i,j},r={x,y};  // 上行更小,优先选择
                            }else if(r.fi!=x){
                                if(r.fi>x)l={i,j},r={x,y};  // 右列更小,优先选择
                            }else if(r.se!=y){
                                if(r.se>y)l={i,j},r={x,y};  // 下行更小,优先选择
                            }
                        }
                    }
        
        if(maxv<=0)break;  // 无有效矩形,退出循环
        
        res+=maxv; 
        cout<<"("<<l.fi<<", "<<l.se<<") ("<<r.fi<<", "<<r.se<<") "<<maxv<<endl;
        
        // 消除选中的矩形:将矩形内所有元素置为0
        for(int i=l.fi;i<=r.fi;i++)
            for(int j=l.se;j<=r.se;j++)
                g[i][j]=0;
        
        // 下落逻辑:每列的非0元素下沉(填补被消除的0)
        for(int i=1;i<=n;i++)  // 按列处理
        {
            for(int j=n;j>=2;j--)  // 从下往上检查,确保下方先填满
                if(g[i][j]==0)  // 若当前位置为0(空位)
                    // 向上寻找非0元素,填补当前空位
                    for(int k=j-1;k>=1;k--)
                        if(g[i][k]){  // 找到非0元素
                            g[i][j]=g[i][k];  // 下移元素
                            g[i][k]=0;  // 原位置置为0
                            break;  // 只填补一个,继续处理上方空位
                        }
        }
            
        // 重新标记0为负无穷
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(g[i][j]==0)
                    g[i][j]=-1e10;
    }
    cout<<res<<endl;
}
signed main() {
    std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int _=1;
    //cin>>_;
    while(_--)solve();
    return 0;
}
满分Kadane's 算法优化:O(\(n^3\))
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define LL long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
using namespace std;
int n,g[110][110];
int sumg[110][110];
int row_sum[110]; // 存储当前上下行范围内每列的和
void solve() {
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            cin>>g[j][i];
            if(g[j][i]==0)g[j][i]=-1e10;
        }
    int res=0;
    while(1){
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                sumg[i][j]=g[i][j]+sumg[i-1][j]+sumg[i][j-1]-sumg[i-1][j-1];
        
        PII l={-1,-1},r={-1,-1};
        int maxv=0;
        
        // 固定上行j和下行y,用Kadane算法找最优左右列
        for(int j=1;j<=n;j++){
            memset(row_sum,0,sizeof(row_sum));
            for(int y=j;y<=n;y++){
                // 计算每列在[j,y]行范围内的和(修复行列对应关系)
                for(int i=1;i<=n;i++)
                    row_sum[i] += g[i][y]; // 累加当前行到列和中
                
                // Kadane算法找最优左右列
                int current_sum=0, left=1;
                for(int x=1;x<=n;x++){
                    if(current_sum <= 0){
                        current_sum = row_sum[x];
                        left = x;
                    } else {
                        current_sum += row_sum[x];
                    }
                    
                    // 计算当前矩形实际和(修复和计算错误)
                    int score = current_sum;
                    if(score <= 0) continue;
                    
                    // 更新最大矩形(保持原字典序逻辑)
                    if(score > maxv){
                        maxv = score;
                        l={left,j}; // 左列, 上行
                        r={x,y};   // 右列, 下行
                    } else if(score == maxv){
                        if(l.fi!=left){
                            if(l.fi>left)l={left,j},r={x,y};
                        }else if(l.se!=j){
                            if(l.se>j)l={left,j},r={x,y};
                        }else if(r.fi!=x){
                            if(r.fi>x)l={left,j},r={x,y};
                        }else if(r.se!=y){
                            if(r.se>y)l={left,j},r={x,y};
                        }
                    }
                }
            }
        }
        
        if(maxv<=0)break;
        res+=maxv;
        cout<<"("<<l.fi<<", "<<l.se<<") ("<<r.fi<<", "<<r.se<<") "<<maxv<<endl;
        
        // 消除选中矩形
        for(int i=l.fi;i<=r.fi;i++)
            for(int j=l.se;j<=r.se;j++)
                g[i][j]=0;
        
        // 下落逻辑(保持原有正确逻辑)
        for(int i=1;i<=n;i++){
            for(int j=n;j>=2;j--){
                if(g[i][j]==0){
                    for(int k=j-1;k>=1;k--){
                        if(g[i][k]!=0){
                            g[i][j]=g[i][k];
                            g[i][k]=0;
                            break;
                        }
                    }
                }
            }
        }
            
        // 重新标记0为负无穷
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(g[i][j]==0)
                    g[i][j]=-1e10;
    }
    cout<<res<<endl;
}
signed main() {
    std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int _=1;
    while(_--)solve();
    return 0;
}
[--------------时间分割线--------------]
2023年
RC-u1 睿抗,启动!(分数 15)

输入样例1:
点击查看代码
DOGcat1234XZxzABabFFXIV
输出样例1:
点击查看代码
DOGcat1234XZxzABabFFXIV
ephBZS1234YAwyBCzaggyjw
输入样例2:
点击查看代码
2
DOGcat1234XZxzABabFFXIV
输出样例2:
点击查看代码
DOGcat1234XZxzABabFFXIV
DOGcat1234ZBvxCDYZFFXIV
思路(字符串处理)
严格按照题目要求处理字符串即可。
C++代码实现
点击查看代码
void solve(){
    int n;
    string s;
    cin>>n>>s;
    if(s == "yourname") s = "accuber";
    
    cout << s << endl;  
    
    // 进行N轮转换
    while(n--){
        string tmp = "";
        int m = (int)s.length(); 
        // 第一轮转换:根据规则替换每个字符
        for(int i = 0; i < m; i++){
            if(s[i] >= 'A' && s[i] <= 'Z'){
                if(s[i] == 'Z') tmp += 'A';
                else tmp += (char)(s[i] + 1);
            }
            else if(s[i] >= 'a' && s[i] <= 'z'){
                if(s[i] == 'a') tmp += 'z';
                else tmp += (char)(s[i] - 1); 
            }
            else tmp += s[i];
        }
        s = "";
        int idx = 0;  // 当前处理位置
        // 第二轮转换:处理连续字符
        while(idx < m){
            int cnt = 0;// 记录连续字符数量
            
            // 处理连续大写字母
            while(idx < m && tmp[idx] >= 'A' && tmp[idx] <= 'Z'){
                idx++, cnt++;
            }
            if(cnt){  // 存在连续大写字母
                if(cnt >= 3){
                    for(int i = idx - cnt; i < idx; i++)
                        s += (char)(tmp[i] - 'A' + 'a');
                }
                else {
                    for(int i = idx - cnt; i < idx; i++)
                        s += tmp[i];
                }
                continue;
            }
            // 处理连续小写字母
            while(idx < m && tmp[idx] >= 'a' && tmp[idx] <= 'z'){
                idx++, cnt++;
            }
            if(cnt){  // 存在连续小写字母
                if(cnt >= 3){
                    for(int i = idx - cnt; i < idx; i++)
                        s += (char)(tmp[i] - 'a' + 'A');
                }
                else {
                    for(int i = idx - cnt; i < idx; i++)
                        s += tmp[i];
                }
                continue;
            }
            // 处理非字母字符(如数字)
            s += tmp[idx++];
        }
    }
    
    cout << s << endl;
}
RC-u2 桌游猜谜(分数 20)

输入样例:
点击查看代码
2
1
1 1 1 1 1 1
3
1 4 2 8 5 7
2 3 1 4 6 6
4 5 3 2 8 1
输出样例:
点击查看代码
59339
7875
思路(暴力枚举+dfs)
1.枚举询问组合:
颜色:三重循环枚举所有不重复的三色组合(共 20 种)。
范围:枚举所有可能的和范围[L,R](3≤L≤R≤24)。
2.DFS 计算方案数:
递归遍历三色的所有可用数字组合,统计和在[L,R]内的方案数K₁,总方案数减K₁得K₂。
3.计算剩余方案数:
总方案数为(8-n)³,剩余方案数=总方案数-最优询问的Max(K₁,K₂)。
C++代码实现
点击查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define int long long 
#define pb push_back 
#define fi first 
#define se second 
#define endl "\n"
using namespace std;
const int N=1e4+10,M=5010,mod=1e9+7,INF=0x3f3f3f3f;
int n,m,k,p;   
int a[N][7];   // 存储其他玩家的卡牌信息(未使用)
int card[7][9];// 标记每种颜色的数字是否被使用(行:颜色1-6,列:数字1-8)
PII ans,tmp;   // ans存储最优的(K1,K2),tmp存储当前询问的(K1,K2)
int qpow(int a,int b){
    int res=1;
    while(b){
        if(b&1)res=res*a;
        a=a*a; 
        b>>=1; 
    }
    return res;
}
// 计算三种颜色数字和在[L,R]内/外的方案数
//c1,c2,c3为三种颜色;idx为当前处理的颜色索引(1-3);l,r为范围;sum为当前和
void dfs(int c1,int c2,int c3,int idx,int l,int r,int sum){
    if(idx>3){  // 已处理完三种颜色,判断和是否在范围内
        if(sum>=l&&sum<=r)tmp.fi++;  // 在范围内,K1加1
        else tmp.se++;               // 不在范围内,K2加1
        return;
    }
    int color;
    if(idx==1)color=c1;
    else if(idx==2)color=c2;
    else color=c3;
    for(int i=1;i<=8;i++){
        if(!card[color][i]){
            card[color][i]=1;
            dfs(c1,c2,c3,idx+1,l,r,sum+i);  // 递归处理下一种颜色,累加当前数字
            card[color][i]=0;
        }
    }
}
// 检查当前询问(三种颜色+范围[L,R])的效果
void check(int c1,int c2,int c3,int l,int r)
{
    tmp={0,0};
    dfs(c1,c2,c3,1,l,r,0);  // 计算K1和K2
    // 更新最优解ans:选择Min(K1,K2)最大的情况
    if(min(tmp.fi,tmp.se)>min(ans.fi,ans.se))ans=tmp;
    else if(tmp.fi==ans.fi&&tmp.se>ans.se)ans=tmp;  // 若Min相等,选择K2更大的
    else if(tmp.se==ans.se&&tmp.fi>ans.fi)ans=tmp;  // 若Min相等,选择K1更大的
}
void solve(){
    cin>>n;
    memset(card,0,sizeof card);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=6;j++)
        {
            cin>>k;
            card[j][k]=1;  // 标记该数字已被使用
        }
            
    ans={-1,-1};
    // 枚举所有可能的询问组合
    //枚举三种不同颜色
    for(int i=1;i<=6;i++)
        for(int j=i+1;j<=6;j++)
            for(int k=j+1;k<=6;k++)
                //枚举范围[L,R](L<=R,和的范围3到24)
                for(int l=3;l<=24;l++)
                    for(int r=l;r<=24;r++)
                        check(i,j,k,l,r);
    
    // 计算剩余方案数:总方案数*(Max(K1,K2)/总方案数) = Max(K1,K2)
    // 注:总方案数为(8-n)^3,而K1+K2=(8-n)^3,故(8-n)^3 * max(K1,K2)/(8-n)^3 = max(K1,K2)
    int res=qpow(8-n,3)*max(ans.fi,ans.se);
    cout<<res<<endl;
}
signed main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    int _ = 1;
    cin >> _;
    while (_--) solve();
    return 0;
}
RC-u3 兰州拉面派餐系统(分数 25)

输入样例:
点击查看代码
3 5 10
3 5 8
2 1 3 3 1 1 2 1 3 2
输出样例:
点击查看代码
2:3 5:3 1:5 6:6 3:8 4:8 7:8 8:8 10:13 9:14
3 3 1 1 2
思路(模拟+小根堆排序)
1.堆的维护:用小根堆unuse存空闲篮子(按编号升序),小根堆box存煮面中篮子(按煮好时间升序,时间同则按客单编号升序)。
2.流程处理:处理客单时,优先取unuse中最小编号篮子;无空闲则等box中最早煮好的,释放后再分配,记录煮好时间。
3.结果处理:所有客单处理完后,清空box并记录出锅时间,按送餐时间和客单编号排序输出,同时统计各篮子煮面数量。
C++代码实现
点击查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define int long long
#define pb push_back
#define fi first
#define se second
#define endl "\n"
using namespace std;
const int N=1e6+10,M=5010,mod=1e9+7,INF=0x3f3f3f3f;
int n,m,k,p;   // n:面种类数;m:篮子数;k:客单数
int id[N];     // id[i]:第i种面的煮制时间
int cnt[N];    // cnt[i]:第i个篮子煮面数量
vector<PII>ans;// 存储客单编号和送餐时间(用于排序输出)
// num:客单编号、boxnum:篮子编号、time:煮好时间
struct nod{
    int num,boxnum,time;
    bool operator < (const nod&u)const{
        if(time!=u.time)return time>u.time;
        return num>u.num; 
    }
};
priority_queue<int,vector<int>,greater<int>>unuse;  // 空闲篮子(小根堆,取编号最小)
priority_queue<nod>box;                             // 正在煮面的篮子(按规则排序)
// 排序函数:按送餐时间升序,时间相同按客单编号升序
bool cmp(PII a,PII b){
    if(a.se!=b.se)return a.se<b.se;
    return a.fi<b.fi;
}
void solve(){
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)cin>>id[i];
    for(int i=1;i<=m;i++)unuse.push(i);
    
    int tt=0;  // 记录当前时间(最近一次有面出锅的时间)
    for(int i=1;i<=k;i++){ 
        cin>>p;  
        
        if(unuse.size()){
            int idx=unuse.top();unuse.pop();  
            //煮好时间=当前时间+面p的煮制时间
            box.push({i,idx,id[p]+tt});
        }else{ 
            auto tmp=box.top();box.pop();
            tt=tmp.time;  // 更新当前时间为该面的出锅时间
            ans.pb({tmp.num,tt});  // 记录该客单的送餐信息
            cnt[tmp.boxnum]++;  // 该篮子煮面数量+1
            unuse.push(tmp.boxnum); 
            
            // 处理同时煮好的其他面(时间相同)
            while(box.size()&&box.top().time==tt){
                ans.pb({box.top().num,tt});
                cnt[box.top().boxnum]++;
                unuse.push(box.top().boxnum);
                box.pop();
            }
            
            // 分配空闲篮子给当前客单
            int idx=unuse.top();unuse.pop();
            box.push({i,idx,id[p]+tt});
        }
    }
    
    // 处理剩余所有正在煮面的篮子
    while(box.size()){
        auto tmp=box.top();box.pop();
        tt=tmp.time;
        ans.pb({tmp.num,tt});
        cnt[tmp.boxnum]++;
        unuse.push(tmp.boxnum);
        while(box.size()&&box.top().time==tt){
            ans.pb({box.top().num,tt});
            cnt[box.top().boxnum]++;
            unuse.push(box.top().boxnum);
            box.pop();
        }
    }
    
    // 按送餐时间和客单编号排序
    sort(ans.begin(),ans.end(),cmp);
    
    cout<<ans[0].fi<<":"<<ans[0].se;
    for(int i=1;i<(int)ans.size();i++)
        cout<<" "<<ans[i].fi<<":"<<ans[i].se;
    cout<<endl;
    
    for(int i=1;i<=m;i++)
        cout<<cnt[i]<<(i==m?'\n':' ');
}
signed main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    int _ = 1;
    while (_--) solve();
    return 0;
}
RC-u4 拆积木(分数 30)

输入样例1(示意如上图):
点击查看代码
5 5
4 4 4 11 11
9 9 4 2 1
9 5 4 4 4
7 3 3 6 10
8 8 8 10 10
输出样例1:
点击查看代码
11 1 2 4 6 9 5 3 7 8 10
输入样例2:
点击查看代码
4 3
8 9 7
4 4 4
5 6 4
1 4 4
输出样例2:
点击查看代码
7 8 9 Impossible
思路(拓扑排序)
问题本质是求解积木拆卸的拓扑排序。每块积木只能在其正上方的所有积木(若存在且不同编号)被拆除后才能拆卸,即存在 “上方积木→当前积木” 的依赖关系。通过建立依赖图的入度表,使用小根堆优先选择编号最小的可拆积木(入度为 0),最终若所有积木均可拆除则输出顺序,否则输出 “Impossible”。
C++代码实现
点击查看代码
#include <bits/stdc++.h>
#define endl "\n"
using namespace std;
const int N=1e6+10,M=2010,mod=1e9+7,INF=0x3f3f3f3f;
int n,m,k;
int g[M][M];  // 存储积木布局,g[i][j]表示第i行第j列的积木编号
bool st[M][M];// 未使用(预留标记数组)
int d[N];     // d[i]:编号为i的积木的入度(依赖的上方积木数量)
int h[N],e[N],ne[N],idx;
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void solve()
{
    memset(h,-1,sizeof h); 
    memset(d,-1,sizeof d);
    cin>>n>>m;
    for(int i=1;i<=n;i++) 
        for(int j=1;j<=m;j++)
        {
            cin>>g[i][j];
            if(i>1){  // 若不是顶端行,需判断与上方积木的关系
                if(g[i-1][j]==g[i][j])continue;  // 同一积木,无依赖
                // 不同积木:下方积木(g[i][j])依赖上方积木(g[i-1][j])
                add(g[i-1][j],g[i][j]);
                // 更新下方积木的入度(首次处理则设为1,否则+1)
                if(d[g[i][j]]==-1)d[g[i][j]]=1;
                else d[g[i][j]]++;
            }else{  // 顶端行积木,入度为0(可直接拆除)
                if(d[g[i][j]]==-1)d[g[i][j]]=0;
            }
        }
    
    // 小根堆:存储当前可拆除的积木(入度为0),保证优先取编号最小的
    priority_queue<int,vector<int>,greater<int>>pq;
    for(int i=1;i<=1e6;i++)
        if(d[i]==0)pq.push(i); 
    
    bool ans=1;
    while(!pq.empty())
    {
        int p=pq.top();pq.pop(); 
        if(ans)cout<<p,ans=0;
        else cout<<" "<<p;
        for(int i=h[p];~i;i=ne[i])
        {
            int j=e[i]; 
            d[j]--;
            if(d[j]==0)pq.push(j);  // 入度为0则加入可拆队列
        }
    }
    
    for(int i=1;i<=1e6;i++)
        if(d[i]>0){
            if(ans)cout<<"Impossible";
            else cout<<" Impossible";
            return;
        }
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);
    int _=1;
    while(_--) solve();
    return 0;
}
RC-u5 栈与数组(分数 30)

输入样例:
点击查看代码
3
6 7 3
1 1 4 5 1 4
1 9 1 9 8 1 0
5 5 2
1 2 3 4 5
6 7 8 9 10
5 5 2
1 1 1 1 1
1 1 1 1 1
输出样例:
点击查看代码
8
10
2

思路(预处理、二分答案+dp)
参考题解
一、pre 数组:预处理剩余元素数
1.核心作用是:计算 “从第一个栈取前 i 个元素、从第二个栈取前 j 个元素” 后,经过所有可能的消除操作(即凑齐 K 个相同元素就删除),最终剩余的元素数量。
2.基础思路:
无论元素从两个栈中以什么顺序取出,最终剩余的元素数是固定的(因为消除只看 “相同元素的总数量”,与顺序无关)。
3.具体计算:
对每个 i(第一个栈取 i 个元素),先处理第一个栈的 i 个元素:
用 cnt 统计每个元素的出现次数,acc 累计当前未消除的元素数。每当某个元素的计数达到 K,就将其计数清零,同时 acc 减去 K(表示消除了 K 个元素)。
再处理第二个栈的 j 个元素:
延续上面的 cnt 和 acc,同样统计并消除,最终得到的 acc 就是 pre[i][j]。
4.示例:
若 K=3,栈 1 前 2 个元素是 [1,1],栈 2 前 1 个元素是 [1]:
处理栈 1 的 2 个1:cnt[1]=2,acc=2;
处理栈 2 的 1 个1:cnt[1]=3(达到K),cnt[1] 清零,acc=2+1-3=0;
因此 pre[2][1] = 0。
二、dp 数组:判断可行性
1.状态定义:
dp[i][j] = 1 表示:存在合法的取法,使得取完第一个栈的 i 个元素和第二个栈的 j 个元素,且过程中数组的临时长度(未消除时的最大长度)始终不超过 x;否则dp[i][j]=0
2.转移逻辑:
dp[i][j] 的值由两种可能的 “最后一步操作” 决定:
最后一步取的是第一个栈的第 i 个元素:
此时前一步的状态是 dp[i-1][j](已取完栈 1 的 i-1 个和栈 2 的 j 个)。
加入第 i 个元素后,临时长度(未消除时)为 pre[i-1][j] + 1(pre[i-1][j] 是前一步消除后的剩余数,加 1 是加入新元素)。
若 pre[i-1][j] + 1 ≤ x 且 dp[i-1][j] = 1,则 dp[i][j] 可行。
最后一步取的是第二个栈的第 j 个元素:
类似地,前一步状态是 dp[i][j-1],加入第 j 个元素后的临时长度为 pre[i][j-1] + 1。
若 pre[i][j-1] + 1 ≤ x 且 dp[i][j-1] = 1,则 dp[i][j] 可行。
3.示例:
若 x=5,pre[2][3] = 4(取 2 个栈 1 和 3 个栈 2 后剩余 4 个),且 dp[1][3] = 1、pre[1][3] + 1 = 3 + 1 = 4 ≤ 5,则 dp[2][3] 可行。
C++代码实现
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define PII pair<int,int>
#define vvi vector<vector<int>>
using namespace std;
const int N=1e3+10,inf=1e10;
int sta1[N],sta2[N],c1,c2,k;
int pre[N][N];//pre[i][j]表示取栈1前i个和栈2前j个元素后剩余的元素数
bool check(int x)  // 检查数组长度x是否可行
{
    vvi dp(c1+1,vector<int>(c2+1,0));
    //dp[i][j]表示:取第一个栈的i个元素和第二个栈的j个元素是否可行(过程中数组临时长度不超过x)
    dp[0][0]=1;// 初始状态:取0个元素可行
    for(int i=0;i<=c1;i++)
    {
        for(int j=0;j<=c2;j++)
        {
            if(!i&&!j) continue;  // 跳过初始状态
            if(pre[i][j]>x) continue; //剩余元素数超过x,不可行
            //从栈1取最后一个元素转移而来
            if(i-1>=0&&pre[i-1][j]+1<=x&&dp[i-1][j]) dp[i][j]=1;
            //从栈2取最后一个元素转移而来
            if(j-1>=0&&pre[i][j-1]+1<=x&&dp[i][j-1]) dp[i][j]=1;
        }
    }
    return dp[c1][c2]==1; //是否能取完所有元素
}
void solve()
{
    cin>>c1>>c2>>k;
    for(int i=1;i<=c1;i++) cin>>sta1[i];
    for(int i=1;i<=c2;i++) cin>>sta2[i];
    
    // 预处理pre数组
    for(int i=0;i<=c1;i++)
    {
        map<int,int> cnt; //统计元素出现次数
        int acc=0; //累计剩余元素数
        
        // 处理栈1前i个元素
        for(int j=1;j<=i;j++)
        {
            int v=sta1[j];
            cnt[v]++;
            acc++;
            if(cnt[v]==k){  // 满k个消除
                cnt[v]=0;
                acc-=k;
            }
        }
        pre[i][0]=acc;  // 栈2取0个时的剩余数
        
        // 处理栈2前j个元素
        for(int j=1;j<=c2;j++)
        {
            int v=sta2[j];
            cnt[v]++;
            acc++;
            if(cnt[v]==k)
            {  // 满k个消除
                cnt[v]=0;
                acc-=k;
            }
            pre[i][j]=acc;  // 栈1取i个、栈2取j个时的剩余数
        }
    }
    
    //二分答案
    int l=1,r=c1+c2;
    while(l<r)
    {
        int mid=l+r>>1;
        if(check(mid))r=mid;
        else l=mid+1;
    }
    cout<<l<<endl;
}
signed main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int _=1;
    cin>>_;
    while(_--) solve();
    return 0;
}
[--------------时间分割线--------------]
2022年
RC-u1 智能红绿灯(分数 15)

输入样例:
点击查看代码
10
3 4 5 6 33 45 49 70 90 100
输出样例:
点击查看代码
18 62
85 129
思路(模拟)
1.遍历所有按钮时间点,维护当前红灯的起始(l)和结束(r)时间,以及是否已延续(flag)。
2.若按钮时间在当前红灯区间外,输出当前区间并生成新区间;若在区间内且未延续,延长红灯并标记已延续。
C++代码实现
点击查看代码
void solve()
{
    int n,k;  // n:按钮按下次数;k:当前按钮时间点
    cin>>n;
    int l=0,r=-1;  // l:当前红灯起始时间;r:当前红灯结束时间(初始为无效值)
    int flag=0;    // 标记当前红灯是否已延续(0:未延续;1:已延续)
    
    for(int i=1;i<=n;i++){
        cin>>k;
        // 若当前按钮时间在当前红灯区间外(需生成新红灯区间)
        if(k>r){
            if(l!=0)cout<<l<<" "<<r<<endl;  // 存在上一个红灯区间
            //起始:k+15(15秒后转红);
            //结束:k+44(30秒红灯,k+15到k+44共30秒)
            l=k+15;
            r=k+45-1;
            flag=0;    // 未延续
        }
        // 若当前按钮在红灯区间内,且未延续过,延长15秒
        else if(flag==0&&k>=l&&k<=r){
            r+=15;     // 结束时间延长15秒
            flag=1;    // 标记为已延续
        }
    }
    // 输出最后一个红灯区间
    cout<<l<<" "<<r<<endl;
}
RC-u2 女王的大敕令(分数 20)



输入样例:
点击查看代码
2 4 4 2
1 2 3 2
5 3 3 4
输出样例:
点击查看代码
2 1 2 4
2 3 3 1
2 3 3 5
思路(暴力枚举+排序)
1.考虑到数据范围较小(棋盘大小固定为5*5),暴力枚举筛选出起始点(第一次攻击安全)和第一次停留点(第二次攻击安全);
2.暴力枚举筛选出来的起始点与停留点,并验证两点间及停留点到目标点的步长是否符合要求;
3.按规则排序并输出所有有效方案。
C++代码实现
点击查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl "\n"
using namespace std;
const int N=1e6+10,M=2010,mod=1e9+7;
int n,m,k;
int c1,c2,r1,r2;       // 北、南、西、东小怪初始位置
int vc1,vc2,vr1,vr2;   // 北、南、西、东小怪移动步数
int row,col,d1,d2;     // 目标点坐标及两次移动步长
bool g1[6][6],g2[6][6];// g1:第一次攻击危险区域;g2:第二次攻击危险区域
struct nod{
	int l,r,ll,rr;
	bool operator < (const nod&u)const{
		if(l!=u.l)return l<u.l;
		if(r!=u.r)return r<u.r;
		if(ll!=u.ll)return ll<u.ll;
		return rr<u.rr;
	}
};
void solve()
{
	cin>>c1>>c2>>r1>>r2;
	cin>>vc1>>vc2>>vr1>>vr2;
	cin>>row>>col>>d1>>d2;
	
	// 计算东西小怪移动后的行
	r1-=vr1;
	r2+=vr2;
	
	// 标记第一次攻击的危险区域
	for(int i=1;i<=5;i++){
		g1[r1][i]=g1[r2][i]=1; 
		g1[i][c1]=g1[i][c2]=1;
	}
	
	// 计算南北小怪移动后的列
	c1+=vc1;
	c2-=vc2;
	
	// 标记第二次攻击的危险区域
	for(int i=1;i<=5;i++){
		g2[r1][i]=g2[r2][i]=1;
		g2[i][c1]=g2[i][c2]=1;
	}
	
	// 筛选第一次攻击中安全的起始点(不在g1中)
	vector<PII>fir,sec;
	for(int i=1;i<=5;i++)
		for(int j=1;j<=5;j++)
			if(!g1[i][j])
				fir.pb({i,j});
	
	// 筛选第二次攻击中安全的第一次停留点(不在g2中)
	for(int i=1;i<=5;i++)
		for(int j=1;j<=5;j++)
			if(!g2[i][j])
				sec.pb({i,j});
				
	vector<nod>ans;
	for(auto i:fir)  // 遍历所有起始点
		for(auto j:sec)  // 遍历所有停留点
		{
			// 验证起始点到停留点的步长是否为d1
			if(abs(i.fi-j.fi)+abs(i.se-j.se)!=d1)continue;
			// 验证停留点到目标点的步长是否为d2
			if(abs(j.fi-row)+abs(j.se-col)!=d2)continue;
			ans.pb({i.fi,i.se,j.fi,j.se});
		}
	
	sort(ans.begin(),ans.end());
	for(auto it:ans){
		cout<<it.l<<" "<<it.r<<" "<<it.ll<<" "<<it.rr<<endl;
	}
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);
    int _=1;
    //cin>>_;
    while(_--) solve();
    return 0;
}
RC-u3 战利品分配(分数 25)

输入样例:
点击查看代码
9 11 2 2
100 150 130 50 30 20 200 0 70
1 2
1 3
2 3
2 4
2 5
3 6
4 7
5 7
6 8
7 9
8 9
1 9
输出样例:
点击查看代码
350
思路(BFS+DP)
本题要求在最短路径限制下,选择从起点 S 到终点 T 的路线,使得第 P 位玩家获得的战利品总价值最大。
1.BFS 分层确定最短路径结构
由于路径必须是最短的,我们可以通过BFS计算每个城市到终点的最短距离(层数),并按距离分层存储。最短路径上的城市必然满足:相邻城市的层数差为 1,且终点T的层数是固定的。
2.DP计算最大价值
定义dp[v]表示到达城市v时,第P位玩家累计获得的最大战利品价值。
辅助数组best[v]用于记录能到达 v 的最优前驱城市的dp值(即从上层城市到 v 的最大dp值)。
状态转移:对于第i层的城市v,其dp[v]由上层(i-1层)城市的dp值转移而来。
若i满足i%K==P-1(即v是第P位玩家分配到的城市),则dp[v]需加上v的战利品价值。
// 对于第i层城市v,从i-1层转移
for (int v : layer[i]) 
{
	dp[v]=best[v];   //继承来自i-1层城市的dp值
	if (i%K==P-1)    // 若v是第P位玩家的分配城市
		dp[v]+=w[v]; // 加上v的战利品价值
}
C++代码实现
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define pb push_back
#define fi first
#define se second
#define endl '\n'
using namespace std;
const int N=1e5+10,INF=0x3f3f3f3f3f3f3f3f;
int n,m,k,p,S,T;     // n节点数,m边数,k间隔,第p个节点,S起点,T终点
int h[N],e[N<<1],ne[N<<1],w[N],idx;
vector<int> layer[N];// 分层存储节点:layer[i]表示距离起点为i的所有节点
int dp[N],best[N];   // dp[i]:到达节点i的最大权值和;best[i]:节点i的最优前驱权值
int d[N];            // d[i]:节点i到起点的最短距离(层数)
void add(int u,int v){
    e[idx]=v,ne[idx]=h[u],h[u]=idx++;
}
void solve()
{
	memset(h,-1,sizeof h);
    memset(d,-1,sizeof d);
    memset(dp,-0x3f,sizeof dp);
    memset(best,-0x3f,sizeof best);
    
	cin>>n>>m>>k>>p;
    for(int i=1;i<=n;i++)cin>>w[i];
	int u,v;
    for(int i=0;i<m;i++)
    {
        cin>>u>>v;
        add(u,v);add(v,u);
    }
    cin>>S>>T;
    // BFS分层:计算各节点到起点的最短距离(层数)
    layer[0].pb(S);
    queue<int>q;
    q.push(S);
    d[S]=0;
    
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int i=h[u];~i;i=ne[i])
        {
            int v=e[i];
            if(d[v]==-1)//未访问
            {
                d[v]=d[u]+1;
                q.push(v);
                layer[d[v]].pb(v);//加入对应层数
            }
        }
    }
    //按层数从1到终点层数递推
    //若p=1(第1个节点),则包含起点权值
    dp[S]=(p==1?w[S]:0);
    for(int i=1;i<=d[T];i++)
    {
        //1.计算当前层节点的最优前驱
        for(int u:layer[i-1])
        {  //遍历上一层所有节点
            if(dp[u]==-INF)continue;  //跳过不可达节点
            // 遍历u的邻接节点,更新当前层节点的best值
            for(int j=h[u];~j;j=ne[j])
            {
                int v=e[j];
                // v是当前层节点,且u的权值更大,更新best[v]
                if(d[v]==i&&dp[u]>best[v])
                    best[v]=dp[u];
            }
        }
        //2.更新当前层节点的dp值
        for(int v:layer[i])
        {
            dp[v]=best[v];  //继承最优前驱的权值
            //当前层数是第p个间隔节点,加上当前节点权值
            if(i%k==p-1)dp[v]+=w[v];
            best[v]=-INF;// 重置,避免影响下一层
        }
    }
    cout<<dp[T]<<endl;
}
signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int _=1;
	//cin>>_;
	while(_--)solve();
    return 0;
}
RC-u4 变牛的最快方法(分数 30)

输入样例:
点击查看代码
13 5 6 20 2 20 1 13 9 20 3 28 3 34 6 25 233 -1
3 5 6 20 6 20 3 5 9 3 9 20 3 6 6 25 233 -1
输出样例:
点击查看代码
8
122212112023121222

思路(编辑距离问题DP)
本题可转化为经典的编辑距离问题,目标是计算将一个序列通过插入、删除、替换这3种操作转换为另一个序列,求所需的最少操作次数和具体操作序列。
1.DP设计:时间复杂度O(\(n^2\))
定义 dp[i][j] 表示将第一个序列的前 i 个元素转换为第二个序列的前 j 个元素的最少操作次数。
(1)边界条件:
dp[i][0] = i(将前 i 个元素全删除)。
dp[0][j] = j(向空序列插入 j 个元素)。
(2)状态转移:对每个 (i,j),计算三种操作的代价并取最小值:
插入:dp[i][j-1] + 1(在第一个序列第 i 个元素前插入第二个序列第 j 个元素)。
删除:dp[i-1][j] + 1(删除第一个序列第 i 个元素)。
替换 / 不变:dp[i-1][j-1] + (a[i] != b[j])(元素不同则替换,相同则不变)。
2.操作序列获取:
用 op[i][j] 记录到达 (i,j) 状态的操作(0删/1改/2不变/3插)。
用 pre[i][j] 记录前驱状态,从 (n,m) 回溯至 (0,0),反转路径得到操作序列。
C++代码实现
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define LL long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
using namespace std;
const int N=1e3+10;
int a[N],b[N],n=0,m=0,k;
int dp[N][N]; // dp[i][j]: a前i个转b前j个的最少操作数
int op[N][N]; // op[i][j]: 到达(i,j)的操作(0删1换2留3插)
PII pre[N][N]; // pre[i][j]: (i,j)的前驱状态,用于回溯
void solve() {
    while(cin>>k && k!=-1) a[++n] = k;
    while(cin>>k && k!=-1) b[++m] = k;
    memset(op,-1,sizeof(op));
    //初始化:a前i个转空序列(全删)
    for(int i=0;i<=n;++i) {
        dp[i][0]=i;        //需要删i个
        op[i][0]=0;        //操作0(删)
        pre[i][0]={i-1,0}; //前驱是i-1,j=0
    }
    //初始化:空序列转b前i个(全插)
    for(int i=0;i<=m;++i) {
        dp[0][i]=i;        //需要插i个
        op[0][i]=3;        //操作3(插)
        pre[0][i]={0,i-1}; //前驱是i=0,j-1
    }
    for(int i=1;i<=n;++i) 
    {
        for(int j=1;j<=m;++j) 
        {
            int op1 = dp[i][j-1]+1;              // 插
            int op2 = dp[i-1][j]+1;              // 删
            int op3 = dp[i-1][j-1]+(a[i]!=b[j]); // 换/留
            dp[i][j] = min(op1,min(op2,op3)); //取最小代价
            // 记录操作和前驱
            if(dp[i][j]==op3) {       // 换/留
                op[i][j] = (a[i]!=b[j] ? 1 : 2); // 不等时换,等时留
                pre[i][j] = {i-1,j-1};
            } else if(dp[i][j]==op1) { // 插
                op[i][j] = 3;
                pre[i][j] = {i,j-1};
            } else {                   // 删
                op[i][j] = 0;
                pre[i][j] = {i-1,j};
            }
        }
    }
    // 输出最少操作数
    cout<<dp[n][m]<<endl;
    // 回溯找操作序列
    PII t={n,m};
    vector<int> ans;
    while(t.fi||t.se) { // 从(n,m)回溯到(0,0)
        ans.pb(op[t.fi][t.se]);
        t = pre[t.fi][t.se];
    }
    reverse(ans.begin(),ans.end()); // 反转得到正确顺序
    for(auto& it:ans) cout<<it;
    cout<<endl;
}
signed main() {
    std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int _=1;
    // cin>>_;
    while(_--) solve();
    return 0;
}
RC-u5 养老社区(分数 30)

输入样例:
点击查看代码
11
1 2
1 3
1 4
2 5
2 6
3 7
3 8
4 9
4 10
1 11
1 2 3 4 5 6 7 8 9 9 10
输出样例:
点击查看代码
14
思路(BFS+暴力枚举)
一、题目要求
树中满足以下条件的三元组数量:
三个节点两两距离相等;
三个节点种类各不相同;
三元组本质不同(元素唯一,不重复计数)。
二、关键思路O(\(n^3\))
1.距离计算:利用 BFS 预处理所有节点对的最短距离(树中两点距离唯一)。
2.三元组枚举:通过三重循环枚举所有三元组 (i<j<k),避免重复计数。
两两距离是否相等(d[i][j] == d[i][k] == d[j][k]);
三个节点种类是否全不同(w[i]、w[j]、w[k] 互不相等)。\
注意:三元组枚举的方法实际上会超时,但是题目数据过水,暴力直接过了。。。
C++代码实现
点击查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define int long long
#define pb push_back
#define fi first
#define se second
#define endl "\n"
using namespace std;
// 常量定义:数组大小、模数、无穷大值
const int N=1e6+10,M=2010,mod=1e9+7,INF=0x3f3f3f3f;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,u,v;
int h[N],ne[N*2],e[N*2],idx;
int w[N],d[M][M];// 距离矩阵: d[i][j]表示i到j的最短距离
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
// BFS计算从p到所有节点的最短距离
void bfs(int p){
    queue<int>qq;
    qq.push(p);
    d[p][p]=0;
    while(qq.size()){
        int idx=qq.front();qq.pop();
        for(int i=h[idx];~i;i=ne[i]){
            int j=e[i];
            if(d[p][j]>d[p][idx]+1){
                d[p][j]=d[p][idx]+1;
                qq.push(j);
            }
        }
    }
}
void solve(){
    memset(h,-1,sizeof h);
    memset(d,0x3f,sizeof d);
    cin>>n;
    for(int i=1;i<n;i++){
        cin>>u>>v;
        add(u,v),add(v,u);
    }
    for(int i=1;i<=n;i++)cin>>w[i];
    // 计算两两节点间的距离
    for(int i=1;i<=n;i++)bfs(i);
    
    int res=0;
    // 枚举所有三元组,确保不重复计数
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
            for(int k=j+1;k<=n;k++)
                // 检查两两距离是否相等
                if(d[i][j]==d[i][k]&&d[i][j]==d[j][k])
                    // 检查三个节点种类是否全不同
                    if(w[i]!=w[j]&&w[i]!=w[k]&&w[j]!=w[k])
                        res++;
    
    cout<<res<<endl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);
    int _=1;
   // cin>>_;
    while(_--)solve();
    return 0;
}
    
 
 
         
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号