Codeforces Round #668 (Div. 2)A-D

时间:2022-07-24
本文章向大家介绍Codeforces Round #668 (Div. 2)A-D,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

A.tation Forgery

题意:

给出一个长度为 n 的排列,排列 p 的定义是,对于任意一个 p[ i ] 来说,其取值范围为 [ 1 , n ] ,且任意两个元素互不相等题目规定函数

F( p ) = sort( [ p_1+p_2 , p_3+p_4 , ... , p_{n-1}+p_n ] )

,解释一下就是,首先将排列 p 相邻的两个元素求和,得到一个长度为 n - 1 的新数组记为数组 q,现在对 q 数组排序,则 F( p ) = 排序后的 q 数组。现在需要求出一个长度为 n 的另一个排列 t,使得 t != q 且 F( t ) = F( p )。

思路:

看到第一个样例就不难想到其中一个构造方法是将排列 p 翻转后直接输出,将排列 p 反转后得到 p' ,将其带入 F 函数得到

F(p')=sort( [ p'_1+p'_2 , p'_3+p'_4 , ... , p'_{n-1}+p'_n ] )=sort([ p_n+p_{n-1} , p_{n-2}+p_{n-3} , ... , p_2+p_1 ])

不难看出排序后两个排列的 F 是相同的,故直接将排列 p 翻转输出即可。

#include<iostream>
#include<cstdio>
using namespace std;
const int MAX_N=101000;
int a[MAX_N];
int main(void){
    int T,n,i;
    cin>>T;
    while(T--){
        scanf("%d",&n);
        for(i=1;i<=n;i++)
        scanf("%d",&a[i]);
        for(i=n;i>=1;i--)
        printf("%d ",a[i]);
        printf("n");
    }
    return 0;
}

B. Array Cancellation

题意:

给出一个长度为 n 的数列 a ,问将其变为 0 的最小花费是多少,可以进行以下两种操作:

  1. 选择 i 和 j 满足 i < j,使得 a[ i ] --,a[ j ] ++,花费为 0。
  2. 选择 i 和 j 满足 i >= j,使得 a[ i ] --,a[ j ] ++,花费为 1 。

思路:

因为题目中保证了

sum_{i=1}^n a_i=0

,所以对正数或者负数单独计算贡献就是答案了,这里我选择对负数统计答案这个题目的贪心策略是,因为当 i < j 时,a[ i ] -- && a[ j ] ++ 的花费为 0,所以可以 O( n ) 遍历一遍每个元素,当 a[ i ] < 0 时,选择前面大于 0 的值与其匹配显然是最优的,如果无法将 a[ i ] 抵消为 0 的话,那么 a[ i ] 就只能选择与后面大于 0 的值进行抵消了,此时需要的花费就是 abs( a[ i ] ) 了。

#include<iostream>
#include<cstdio>
using namespace std;
const int MAX_N=101000;
long long a[MAX_N];
int main(void){
    int T,n,i;
    cin>>T;
    while(T--){
        scanf("%d",&n);
        for(i=1;i<=n;i++)
        scanf("%lld",&a[i]);
        long long sum=0,ans=0;
        for(i=1;i<=n;i++){
            if(a[i]<0){
                long long mmin=min(sum,-a[i]);
                sum-=mmin;
                a[i]+=mmin;
                ans-=a[i];
            }
            else
            sum+=a[i];
        }
        printf("%lldn",ans);
    }
    return 0;
}

C.Balanced Bitstring

题意:

给出一个长度为 n 的01字符串 s,规定 s 为 “k -balanced” 的条件是,s 中所有长度为 k 的子串中 0 的个数和 1 的个数相同,题目保证了 k 一定是偶数,现在给出一个仅含有 ' 0 ' , ' 1 ' 和 ' ? ' 的字符串,问可否将 ' ? ' 转换为 ' 0 ' 或 ' 1 ' 从而使得将其变为 “k -balanced”。

思路:

首先需要根据已知条件观察性质,设字符串 s 的下标是从 0 开始的,表示为 s[ 0 ]s[ 1 ] ... s[ n ],现在假设 k 为 4 ,画一下图:

在这里插入图片描述

假设 s[ 1 : 4 ] 这个子串中满足 ' 0 ' 的数量等于 ' 1 ' 的数量,那么考虑与其相邻的前面的那个子串 s[ 0 : 3 ] ,因为 s[ 1 : 3 ] 这个子串是共用的,如果 s[ 0 : 3 ] 也想满足这个约束的话,显然是需要让 s[ 0 ] = s[ 4 ] 才行,同理考虑与其相邻的后面的那个子串 s[ 2 : 5 ] ,也能得到 s[ 1 ] = s[ 5 ] 这个结论,推广一下,就能得出需要满足的第一个条件就是,对于任意一个位置 i 来说,需要满足 s[ i ] == s[ i + k ],在这个题目中的话,问号特判一下就可以了再仔细观察一下第一个条件的约束条件是 s[ i ] = s[ i + k ],这也就相当于对 k 取余的一个划分,到此为止再反观一下这个题目,不难发现 s 中所有长度为 k 的子串,其本质就是 k 的划分,所以我们在判断第一个条件的同时,可以另外开一个数组 str 记录一下这个划分每个位置的值,如果可以通过条件 1 的筛选,那么 str 中的每个元素只能是 ' 0 ' , ' 1 ' 或 ' ? ' ,最后再判断一下 str ,也就是 k 的划分是否满足 “ 0 的个数和 1 的个数相等 ” 就好了,这个比较简单就不赘述了。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAX_N=301000;
char s[MAX_N],str[MAX_N];
int n,k;
bool check1(){
    int i,j;
    for(i=0;i<k;i++)
    str[i]=0;
    for(i=0;i<k;i++){
        for(j=i;j<n;j+=k){
            if(s[j]=='?')
            continue;
            if(!str[i])
            str[i]=s[j];
            else if(str[i]!=s[j])
            return false;
        }
    }
    return true;
}
bool check2(){
    int cnt1=0,cnt0=0;
    for(int i=0;i<k;i++){
        if(str[i]=='0')
        cnt0++;
        else if(str[i]=='1')
        cnt1++;
    }
    if(cnt1<=k/2&&cnt0<=k/2)
    return true;
    else
    return false;
}
int main(void){
    int T;
    cin>>T;
    while(T--){
        scanf("%d%d",&n,&k);
        scanf("%s",s);
        if(!check1()){
            puts("NO");
            continue;
        }
        if(!check2()){
            puts("NO");
            continue;
        }
        puts("YES");
    }
    return 0;
}

D.Tree Tag

题意:

(这道题的题面可能有点歧义,我按照自己的理解AC的这个题)个人理解:A 和 B 两个人在一棵 n 个点的树上玩游戏,初始时 A 在点 a ,B 在点 b ,树上每条边的边权都为 1 ,每次 A 可以移动到距离不超过 da 的顶点,同理 B 可以移动到距离不超过 db 的顶点,问 A 能否到达点 B 所在的顶点,换句话说就是 A 追击 B,问能否追到,每次都是 A 先进行移动。

思路:

看第二个样例的解释,应该不难想到点 B 可以获胜的情况就是:1. A 在第一步无法追上 B2. db > da * 23. 树的直径 > da * 2 其余情况都是 A 获胜稍微解释一下为什么是这样的吧,第一个情况不用解释了吧,如果开局 A 就可以追上 B 的话,那显然 A 是必胜的,只有当开局 A 无法追到 B 且 db >

da*2

时,B 才可以像样例 2 那样一直 “反复横跳”,以至于 A 永远抓不到 B,不过别忘了 B 可以反复横跳的前提还需要满足,在树上存在着这样的一条链可以让他不断的跳,贪心去想,找到树上最长的链作为这条链是最优的,设 max_len 为最长的那条链,再判断一下 max_len 和

da*2

的大小关系就好了具体实现的话,开局判断 A 是否可以追上 B,可以以点 A 为根节点 O( n ) 跑一遍 dfs 求每个点的 deep,那么 deep[ b ] 就是 A 和 B 初始时的距离了,max_len 是树上最长的那条链,其实也就是树的直径,dfsbfs树形dp 都可以轻松求解,这里我用的是树形dp。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int MAX_N=1e5+100;
vector<int>v[MAX_N];
int dp[MAX_N],ans,deep[MAX_N];
void dfs(int x,int fa,int dep){//get_deep
    int i;
    deep[x]=dep;
    for(i=0;i<v[x].size();i++){
        int y=v[x][i];
        if(y==fa)
        continue;
        dfs(y,x,dep+1);
    }
}
void dfs(int x,int fa){//get_mmax
    int i;
    dp[x]=0;
    for(i=0;i<v[x].size();i++){
        int y=v[x][i];
        if(y==fa)
        continue;
        dfs(y,x);
        ans=max(ans,dp[x]+dp[y]+1);
        dp[x]=max(dp[x],dp[y]+1);
    }
}
int main(void){
    int T,n,a,b,da,db,i,x,y;
    cin>>T;
    while(T--){
        scanf("%d%d%d%d%d",&n,&a,&b,&da,&db);
        for(i=1;i<=n;i++)
        v[i].clear();
        for(i=1;i<n;i++){
            scanf("%d%d",&x,&y);
            v[x].push_back(y);
            v[y].push_back(x);
        }
        dfs(a,-1,0);
        if(deep[b]<=da){
            puts("Alice");
            continue;
        }
        if(da*2>=db){
            puts("Alice");
            continue;
        }
        ans=0;
        dfs(1,-1);
        if(da*2>=ans)
        puts("Alice");
        else
        puts("Bob");
    }
    return 0;
}

温馨提示