CodeCraft-20 (Div. 2)【ABCDE】(题解)

涵盖知识点:找规律、图论、状压dp

比赛链接:传送门

A - Grade Allocation

题意: 你可以在卷面分\(m\)以内、保持平均分不变的情况下任意修改每个人的分数,问你最高可以把自己改成几分?
题解: \(ans=min\{sum(score),m\}\)
Accept Code:

#include <bits/stdc++.h>
using namespace std;

int main(){
    int t;
    cin>>t;
    while(t--){
        int n,m;
        cin>>n>>m;
        int sum=0;
        while(n--){
            int a;
            cin>>a;
            sum+=a;
        }
        cout<<min(sum,m)<<"\n";
    }
    return 0;
}

B - String Modification

题意: 给定一个长度为\(n\)的字符串\(s\)。现规定一种操作。规则如下:
选定一个\(k\),\((1\le k\le n)\),依次从\(1\)开始到\(n-k+1\)的下标向后截取长度为\(k\)的字符串并将其翻转。问\(k\)取多少可以使得操作后的字符串字典序最小。
题解: 找规律。取\(k\)时操作后的字符串如下:

  1. 前部:\(s[k:len]\)
  2. 后部:
    1)剩余长度为奇数:原串前部反转。
    2)剩余长度为偶数:原串前部。
    所以,我们只需要遍历一遍\(k\)得出最小值即可。
    Accept Code:
#include <bits/stdc++.h>
using namespace std;

int main(){
    int t;
    cin>>t;
    while(t--){
        int n;
        string s;
        cin>>n>>s;
        string res=s;
        int ans=1;
        for(int k=1;k<n;k++){
            string a=s.substr(0,k);
            string b=s.substr(k);
            if((n-k)&1)
                reverse(a.begin(),a.end());
            b+=a;
            if(b<res){
                res=b;
                ans=k+1;
            }
        }
        cout<<res<<"\n"<<ans<<"\n";
    }
    return 0;
}

C - Primitive Primes

题意: 给定两个多项式,问相乘后哪一项的系数不能被p整除。
题解: 从低位向高位扫描。设多项式\(A\)的第一个不能被p整除的项系数为\(a_i\),多项式\(B\)的第一个不能被p整除的项系数为\(b_j\),则最后所得答案为\(i+j\)。
证明: 若存在其他的数对\((x,y)\)满足\(x+y=i+j\),则\(x<i\)或\(y<j\)一定有一条成立。那么对所得项系数的贡献一定是\(p\)的倍数。而\(i+j\)一定不是\(p\)的倍数,所以答案一定正确。
Accept Code:

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int a[maxn],b[maxn];
int main(){
    int n,m,p;
    cin>>n>>m>>p;
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]);
    }
    for(int i=0;i<m;i++){
        scanf("%d",&b[i]);
    }
    int i=0,j=0;
    while(i<n&&a[i]%p==0)i++;
    while(j<m&&b[j]%p==0)j++;
    cout<<i+j<<"\n";
    return 0;
}

D - Nash Matrix

题意: 每个方块上有5个字母(L、R、U、D、X)之一,分别代表在该位置应该向左右上下或停在原地不动。现在给你从每个点出发最终会到达的位置(若最后会在一群点之间反复横跳就给你\((-1,-1)\))问你是否能够还原每个方块上的字母,若能并还原。
题解: 对于所有\(x_{i,j}=i\)且\(y_{i,j}=j\)的点为起点进行BFS,对于所有\((-1,-1)\)的点向周围扫描其他\((-1,-1)\)的点BFS即可。
Accept Code:

#include <bits/stdc++.h>
using namespace std;
const int maxn=1010;
typedef pair<int,int> pii;
int a[maxn][maxn],b[maxn][maxn];
int vis[maxn][maxn];
int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
queue<pii> q;
void bfs(int x,int y,int t){
    while(!q.empty())q.pop();
    q.push({x,y});
    if(t==0)vis[x][y]=5;
    while(!q.empty()){
        pii tmp=q.front();
        q.pop();
        for(int i=0;i<4;i++){
            int dx=tmp.first+dir[i][0],dy=tmp.second+dir[i][1];
            if(vis[dx][dy])continue;
            if(t==-1){
                if(a[dx][dy]==-1&&b[dx][dy]==-1){
                    vis[dx][dy]=i+1;
                    q.push({dx,dy});
                }
            }else if(a[dx][dy]==x&&b[dx][dy]==y){
                vis[dx][dy]=i+1;
                q.push({dx,dy});
            }
        }
    }
}
char dtc(int x){
    if(x==1)return 'L';
    if(x==2)return 'R';
    if(x==3)return 'U';
    if(x==4)return 'D';
    if(x==5)return 'X';
}
int dtd(int x){
    if(x==0)return 2;
    if(x==1)return 1;
    if(x==2)return 4;
    return 3;
}
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            scanf("%d%d",&a[i][j],&b[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(vis[i][j])continue;
            if(a[i][j]==i&&b[i][j]==j){
                bfs(i,j,0);
            }else if(a[i][j]==-1&&b[i][j]==-1){
                for(int k=0;k<4;k++){
                    int dx=i+dir[k][0],dy=j+dir[k][1];
                    if(a[dx][dy]==-1&&b[dx][dy]==-1){
                        vis[i][j]=dtd(k);
                        bfs(i,j,-1);
                        break;
                    }
                }
            }
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(vis[i][j]==0){
                cout<<"INVALID\n";
                return 0;
            }
        }
    }
    cout<<"VALID\n";
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            printf("%c",dtc(vis[i][j]));
        }
        cout<<"\n";
    }
    return 0;
}

E - Team Building

题意: 一共\(n\)个人,要选\(p\)人作为队员安排在每个位置(每个人在不同位置所产生的贡献不同),另选\(k\)人作为观众。问怎么安排使得最后的总贡献最大。
题解: 观察到\(p\)的范围很小,所以考虑状压DP。首先我们按照每个人作为观众的贡献从大到小进行排序。可以得出观众只能在前\(k+p\)人中选取。因为如果后面的人作为观众,前面的人至少有1人不被选中,选他作为观众明显为更优解。\(dp_{i,j}\)考虑前\(i\)个人选取队员的状态为\(j\)时的最优值,只有观众数量还没有满并且排序后编号不大于\(k+p\)的时候这个人才能考虑成为观众。然后对于不同的状态全部扫描一遍即可。注意初始要把所有状态赋值为负无穷大防止一些非法状态对结果的影响。
Accept Code:

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
const int inf=0x3f3f3f3f;
struct Node{
    int a,p[7];
    bool operator <(const Node &b)const{
        return a>b.a;
    }
}s[maxn];
ll dp[maxn][200];
int main(){
    int n,p,k;
    cin>>n>>p>>k;
    for(int i=1;i<=n;i++){
        cin>>s[i].a;
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<p;j++){
            cin>>s[i].p[j];
        }
    }
    sort(s+1,s+1+n);
    memset(dp,-inf,sizeof dp);
    dp[0][0]=0;
    for(int i=1;i<=n;i++){
        for(int j=0;j<(1<<p);j++){
            if(i-__builtin_popcount(j)<=k&&i<=k+p)
                dp[i][j]=max(dp[i][j],dp[i-1][j]+s[i].a);
            else
                dp[i][j]=dp[i-1][j];
            for(int t=0;t<=p;t++){
                if(j&(1<<t)){
                    dp[i][j]=max(dp[i][j],dp[i-1][j-(1<<t)]+s[i].p[t]);
                }
            }
        }
    }
    cout<<dp[n][(1<<p)-1]<<"\n";
    return 0;
}
上一篇:CodeCraft-20 (Div. 2) 总结


下一篇:CodeCraft-20 (Div. 2)E(状压DP)