【Codeforces Round #531 (Div. 3)】 A.B.C.D.E.F

前言

由于两个号都不算分,所以开了一个小小号来打这场div3,开赛前和学弟开了一场vp,状态不好血崩,之后睡了一会洗把脸,状态大增,感觉特别清醒,之后就比较顺利的1A了5个题,还有一个小时的时间看F,看到数据范围只有16,不由自主地写了dfs,TLE On 13 ,之后还有半小时,想了一下就是建图找哈密顿回路,但是之前没写过这个经典的状压dp问题,遂GG。
biubiubiu r a t i n g + = 206 rating+=206 rating+=206 1500->1706


A.Integer Sequence Dividing

题意

给你一个n个数,权值是1,2,3,4…n,问把n个数分成两组,两组的最小差是多少?
1 < = n < = 2 ∗ 1 0 9 1<=n<=2*10^9 1<=n<=2109

做法

由于1,2,3…n可以凑出小于 n × ( n + 1 ) 2 \frac{n\times \left( n+1 \right)}{2} 2n×(n+1)的所有数,
所以只要判断 n × ( n + 1 ) 2 \frac{n\times \left( n+1 \right)}{2} 2n×(n+1)是否为偶数即可。

代码

#include
typedef long long ll;
int main()
{
    ll n;
    scanf("%lld",&n);
    ll ans=(1LL*(1+n)*n)/2;
    if(ans%2==0) printf("0\n");
    else printf("1\n");
    return 0;
}

B. Array K-Coloring

题意

给定n和k, 1 < = n , k < = 5000 1<=n,k<=5000 1<=n,k<=5000,和n个数
要求用k种颜色对n个数进行涂色,要求每种颜色至少出现过一次,
而且同一种颜色不能对相同的数字涂色,求是否有合法的涂色方案。

做法

首先如果一个数字出现次数超过k,肯定没有合法的涂色方案
如果n 之后就先用1-k涂前1-k个数字,之后暴力判断每个数字可以用哪个颜色,
可以的话就涂色并用二维数组记录,vis[i][j]表示第i种颜色涂过j数字。

代码

#include
const int maxn = 1e5+5;
int a[maxn];
int sum[maxn];
int ans[maxn];
bool vis[5005][5005];
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) sum[a[i]]++;
    for(int i=1;i<=5000;i++)
    {
        if(sum[i]>k)
        {
            printf("NO\n");
            return 0;
        }
    }
    if(n<k)
    {
        printf("NO\n");
        return 0;
    }
    int pos=1;
    for(int i=1;i<=n;i++)
    {
        if(pos<=k)
        {
            ans[i]=pos;
            vis[pos][a[i]]=1;
            pos++;
        }
        else
        {
            for(int j=1;j<=k;j++)
            {
                if(vis[j][a[i]]==0)
                {
                    ans[i]=j;
                    vis[j][a[i]]=1;
                    break;
                }
            }
        }
    }
    printf("YES\n");
    for(int i=1;i<=n;i++) printf("%d ",ans[i]);
    return 0;
}


C. Doors Breaking and Repairing

题意

有n个门,每个门的生命值为 a i a_i ai,一个守护者和一个攻击者,攻击者的攻击力为a,守护者的守护值为b,攻击者先进行攻击,每次可以让一个门的生命值减小x,如果门的生命值小于等于0,则门被损坏,攻击者攻击之后守护者可以把一个没有被损坏的门生命值增加y,问无数个回合之后,有多少个门被损坏。

做法

如果攻击力大于守护值u,攻击者可以慢慢摧毁所有门,
否则,如果生命值小于等于x的门的个数为num
那么攻击者的策略一定是攻击这些门,守护者的策略一定是守护这些门
由于攻击者先手,可以摧毁掉的门的个数为 ⌊ n u m + 1 2 ⌋ \lfloor \frac{num+1}{2} \rfloor 2num+1
代码

#include
int main()
{
    int n,x,y,z;
    scanf("%d%d%d",&n,&x,&y);
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&z);
        if(z<=x) ans++;
    }
    if(x>y)  printf("%d\n",n);
    else  printf("%d\n",(ans+1)/2);
    return 0;
}


D. Balanced Ternary String

题意

给你一个只包含0,1,2的字符串,每次可以把一个字符改变成0,1,2中的一个字符,最后要使字符串中的0,1,2的个数相等,而且要求字符串字典序最小,输出最后的字符串。

做法

最后的0,1,2的个数等于串长/3,所以我们知道0,1,2分别要变化的个数,之后分六种情况考虑即可
设置0的个数为num0,1的个数为num1,2的个数为num2,平均个数为num
情况1: num0<=num,num1<=num,num2>=num
从前往后遍历先把遇见的2都变为0,0的个数足够之后把遇见的2都变为1
情况2: num0<=num,num2<=num,num1>=num
从前往后遍历,把遇见的1都变成0
从后往前遍历,把遇见的1都变成2
情况3: num1<=num,num2<=num,num0>=num
从后往前遍历先把遇见的0都变为2,2的个数足够之后把遇见的0都变为1
情况4: num0>=num,num1>=num,num2<=num
从后往前能变2就变2
情况5: num0>=num,num2>=num,num1<=num
从前往后遍历,把遇见的2都变成1
从后往前遍历,把遇见的0都变成1
情况6: num1>=num,num2>=num,num0<=num
从前往后能变0就变0
代码

#include
const int maxn = 3e5+5;
char str[maxn];
int sum[3];
int ans[3];
int main()
{
    int n;
    scanf("%d",&n);
    scanf("%s",str);
    for(int i=0;i<n;i++) sum[str[i]-'0']++;
    int fin=n/3;
    if(sum[0]<=fin&&sum[1]<=fin&&sum[2]>=fin)
    {
        ans[2]=sum[2]-fin;
        ans[0]=fin-sum[0];
        ans[1]=fin-sum[1];
        for(int i=0;i<n;i++)
        {
            if(str[i]=='2'&&ans[2]>0)
            {
                ans[2]--;
                if(ans[0]>0)
                {
                    str[i]='0';
                    ans[0]--;
                }
                else
                {
                    str[i]='1';
                    ans[1]--;
                }
            }
        }
        printf("%s",str);
    }
    else if(sum[0]<=fin&&sum[2]<=fin&&sum[1]>=fin)
    {
        ans[1]=sum[1]-fin;
        ans[0]=fin-sum[0];
        ans[2]=fin-sum[2];
        for(int i=0;i<n;i++)
        {
            if(ans[0]==0) break;
            if(str[i]=='1'&&ans[1]>0)
            {
                ans[1]--;
                ans[0]--;
                str[i]='0';
            }
        }
        for(int i=n-1;i>=0;i--)
        {
            if(ans[2]==0) break;
            if(str[i]=='1'&&ans[1]>0)
            {
                ans[1]--;
                str[i]='2';
                ans[2]--;
            }
        }
        printf("%s",str);
    }
    else if(sum[1]<=fin&&sum[2]<=fin&&sum[0]>=fin)
    {
        ans[0]=sum[0]-fin;
        ans[1]=fin-sum[1];
        ans[2]=fin-sum[2];
        for(int i=n-1;i>=0;i--)
        {
            if(str[i]=='0'&&ans[0]>0)
            {
                ans[0]--;
                if(ans[2]>0)
                {
                    ans[2]--;
                    str[i]='2';
                }
                else
                {
                    ans[1]--;
                    str[i]='1';
                }
            }
        }
        printf("%s",str);
    }
    else if(sum[0]>=fin&&sum[1]>=fin&&sum[2]<=fin)
    {
        ans[0]=sum[0]-fin;
        ans[1]=sum[1]-fin;
        ans[2]=fin-sum[2];
        for(int i=n-1;i>=0;i--)
        {
            if(str[i]=='1'&&ans[1]>0)
            {
                ans[1]--;
                str[i]='2';
            }
            else if(str[i]=='0'&&ans[0]>0)
            {
                ans[0]--;
                str[i]='2';
            }
        }
        printf("%s",str);
    }
    else if(sum[0]>=fin&&sum[2]>=fin&&sum[1]<=fin)
    {
        ans[0]=sum[0]-fin;
        ans[2]=sum[2]-fin;
        ans[1]=fin-sum[1];
        for(int i=0;i<n;i++)
        {
            if(str[i]=='2'&&ans[2]>0)
            {
                str[i]='1';
                ans[2]--;
            }
        }
        for(int i=n-1;i>=0;i--)
        {
            if(str[i]=='0'&&ans[0]>0)
            {
                str[i]='1';
                ans[0]--;
            }
        }
        printf("%s",str);
    }
    else
    {
        ans[0]=fin-sum[0];
        ans[1]=sum[1]-fin;
        ans[2]=sum[2]-fin;
        for(int i=0;i<n;i++)
        {
            if(str[i]=='1'&&ans[1]>0)
            {
                str[i]='0';
                ans[1]--;
            }
            else if(str[i]=='2'&&ans[2]>0)
            {
                str[i]='0';
                ans[2]--;
            }
        }
        printf("%s",str);
    }
    return 0;
}


E. Monotonic Renumeration

题意

给你一个数组a,构造一个数组b,构造规则是:
b 1 = 0 b_1=0 b1=0
对 于 每 一 对 1 < = i , j < = n , 如 果 a i = a j , 那 么 b i = b j 对于每一对1<=i,j<=n,如果a_i=a_j,那么b_i=b_j 1<=i,j<=n,ai=aj,bi=bj
对 于 每 一 个 i ∈ [ 1 , n − 1 ] , b i = b i + 1 或 者 b i + 1 = b i + 1 对于每一个i\in \left[ 1,n-1 \right] ,b_i=b_{i+1}或者b_i+1=b_{i+1} i[1,n1],bi=bi+1bi+1=bi+1
做法

很显然如果两个数相等,那么他们中间这一段肯定都相等。
所以对于每个数我们可以得到一段线段,左端点是这个数第一次出现的地方,
右端点是这个数最后一次出现的地方,问题就转化为经典的线段覆盖问题,求一下最后有多少个不连续的段,答案就是 2 n u m − 1 2^{num-1} 2num1
由于这道题左端点一定是按照访问顺序的,所以我们可以动态的维护一个到上一段位置的最右端点下标right,如果right>=i我们更新right,否则说明出现不连续的段,答案*2之后再更新right.
代码

#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
const int Mod=998244353;
map<int,int> mp;
int a[maxn];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) mp[a[i]]=i;
    int right=1;
    ll ans=1;
    for(int i=1;i<=n;i++)
    {
        if(right>=i) right=max(right,mp[a[i]]);
        else
        {
            ans=ans*2LL%Mod;
            right=max(right,mp[a[i]]);
        }
    }
    printf("%lld\n",ans);
    return 0;
}


F. Elongated Matrix

题意

给你一个n行m列的矩阵,我们由第一行第一列出发,从上向下走完第一列,之后再从第二行第一列出发,重复这个过程直到所有的格子都走完,我们可以得到一个路径序列,定义k为序列中所有相邻两数值的差的绝对值的最小值,现在我们可以交换矩阵的每一行,问交换之后k最大是多少。

1 < = n < = 16 1<=n<=16 1<=n<=16
1 < = m < = 1 0 4 1<=m<=10^4 1<=m<=104
做法

首先我们想一个暴力一点的做法,如果能枚举所有行的交换情况,那么我们只要算出是否相邻两行满足条件及第一行和最后一行满足条件即可,但是n=16明显不可以,之后我们只枚举第一行和最后一行,并对中间的所有行建边,边权就是能让这两行相邻的最大的k,那么如果我们可以通过这些边从第一行走到最后一行,这条路径上所有边权的最小值就是答案,问题就转换为一个有向图,给定起点终点,求哈密顿回路,我们设dp[i][j][k]为i状态下j为起点k为终点的路径上的最小值,定义vis[i][j][k]为状态i下j为起点是否可以到达k,之后我们就可以类似经典的状压dp做出这道题。
首先,dp的初始状态要把每个点到自己本身联通,也就是

	for(int i=1;i<=n;i++)  vis[1<<(i-1)][i][i]=1;

从小到大枚举所有状态,在每种状态中,枚举所有可能为起点的j,再枚举所有当前状态不包含的点k,对于每个k,再去找每个存在于状态i中的p,设minn[i][j]为i->j连边的最小差的绝对值,那么我们可以得到转移方程

	dp[i+(1<<(k-1))][j][k]=max(dp[i+(1<<(k-1)),dp[i][j][p]+minn[p][k])

最后对所有状态为1<<(n-1)的dp值,求出让首尾合法的k,再取min即可。

坑点
当只有一行的时候需要特判掉。
代码

#include
#include
#include
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 1e5+5;
int dp[1<<16][17][17];
int vis[1<<16][17][17];
int minn[17][17];
int minn2[17][17];
int pic[17][10005];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            scanf("%d",&pic[i][j]);
        }
    }
    if(n==1)
    {
        int ans=INF;
        for(int i=2;i<=m;i++) ans=min(ans,abs(pic[1][i]-pic[1][i-1]));
        printf("%d",ans);
        return 0;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            minn[i][j]=INF;
            minn2[i][j]=INF;
            for(int k=1;k<=m;k++)
            {
                minn[i][j]=min(minn[i][j],abs(pic[i][k]-pic[j][k]));
            }
            for(int k=2;k<=m;k++)
            {
                minn2[i][j]=min(minn2[i][j],abs(pic[i][k]-pic[j][k-1]));
            }
        }
    }
    for(int i=1;i<=n;i++)
        vis[1<<(i-1)][i][i]=1;
    int ans=0;
    for(int i=1;i<(1<<n);i++)
    {
        int cnt=0;
        for(int j=1;j<=n;j++)
        {
            if(i&(1<<(j-1)))
            {
                cnt++;
            }
        }
        if(i==(1<<n)-1)
        {
            for(int j=1;j<=n;j++)
            {
                for(int k=1;k<=n;k++)
                {
                    if(vis[i][j][k]) ans=max(ans,min(dp[i][j][k],minn2[j][k]));
                }
            }
        }
        for(int j=1;j<=n;j++)
        {
            for(int k=1;k<=n;k++)
            {
                if(vis[i][j][k]==0) continue;
                for(int p=1;p<=n;p++)
                {
                    if(i&(1<<(p-1))) continue;
                    vis[i+(1<<(p-1))][j][p]=1;
                    if(cnt==1) dp[i+(1<<(p-1))][j][p]=max(dp[i+(1<<(p-1))][j][p],minn[k][p]);
                    else dp[i+(1<<(p-1))][j][p]=max(dp[i+(1<<(p-1))][j][p],min(minn[k][p],dp[i][j][k]));
                }
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

你可能感兴趣的:(Codeforces,个人训练计划)