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

Codeforces Round #668 (Div. 2)A-D题解
//写于rating值2033/2184
//最近放了个小假加上最近家里事情多,拖延到现在才补掉这场的题解
//今天回学校了

比赛链接:https://codeforces.ml/contest/1405
A题
简单思维水题

题意为对于一个长度为n的排列,我们定义一个特殊运算fingerprintf运算,为计算这个长度为n的排列中每一个相邻两个数字的和之后(显然这样的运算会得到n-1个数字),sort排序后的结果。
现在给定一个排列a,需要你构造出一个长度与a相同的排列b,使得两个排列在fingerprintf运算下结果相同。

我们注意到最后的结果是sort过的,也就是说数字之间的先后顺序是没有关系的,只要相同值的数字出现次数相同就可以了。
那我们直接把原排列反向输出一遍就可以了…不解释

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        int n;
        cin>>n;
        vector<int>num(n);
        for(int i=n-1;i>=0;i--) cin>>num[i];
        for(int i=0;i<n;i++)
        {
    
    
            if(i) cout<<' ';
            cout<<num[i];
        }
        cout<<endl;
    }
}

B题
简单思维,施行,贪心

题意为有一个长度为n的数列num[],数列中所有数字的和为0。
现在你的目标是进行若干次操作后,使得数列中的每一个数字都变为0。
每次操作你可以选择两个下标i和j,对num[i]进行-1操作并对num[j]进行+1操作,如果i<j则此次操作无消耗,如果i>j则此次操作需要消耗一枚硬币。
现在需要你求出最少需要消耗的硬币数量。

这里先用贪心去思考,我们从左侧开始向右侧看过去,出现的正数部分可以和当前位置右侧的负数进行无消耗操作,负数部分则只能和当前位置左侧的整数进行无消耗操作。
对于我们尽可能多使用无消耗操作的思路,那么出现位置越靠左的负数就越不利。因此我们可以直接采取贪心的策略,用一个temp记录当前位置左侧出现了总和多少的正数,并与负数进行无消耗的抵消操作。这个过程中如果出现temp<0则代表当前位置的负数已经没有左侧的正数可以与之进行无消耗抵消操作了,必然要与右侧的正数进行消耗硬币的抵消操作,此时直接累加temp的绝对值到最后答案次数上并将temp置零即可。

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        int n;
        cin>>n;
        vector<ll>num(n);
        for(auto &x:num) cin>>x;
        ll ans=0;
        ll temp=0;//temp记录我们当前位置左侧还有多少的正数可用
        for(int i=0;i<n;i++)
        {
    
    
            temp+=num[i];//下面的if运算保证了temp一定不是负数
            //temp与当前位置上的数字进行加法运算,如果num[i]是负数则正是题意中无消耗的操作
            if(temp<0)//如果出现了无法被无消耗操作除去的负数部分,则必然要与后面的正数进行有消耗的操作
                //此处累加temp的绝对值到答案上,并清零
            {
    
    
                ans-=temp;
                temp=0;
            }
        }
        if(temp<0) ans-=temp;
        cout<<ans<<endl;
    }
}

C题
简单结论,贪心,施行

题意为给定一个长度为n的字符串s,该字符串s只包含字符’0’,‘1’,’?‘三种。并给定一个偶数k,现在询问是否可以对字符串s中的字符’?'替换成0或者1后,满足字符串s中所有长度为k的连续子串,子串中0和1出现的个数相等,也就是均为k/2。

先推一个小结论,

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const ll maxn=3e5+7;

char firt[maxn];//firt[x]记录字符串s的下标i中,所有满足i%k=x的i,对应的字符是否相同,初始为'?'
//上述条件的所有字符相同,是字符串s的所有长度为k的子串中,0和1出现次数相同的充要条件
//这个条件是题目限定的所有长度为k子串中,0出现的次数均等于1出现的次数这个条件的弱化版本,为前置条件
int n,k;
string s;

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        cin>>n>>k>>s;
        for(int i=0;i<k;i++) firt[i]='?';
        bool flag=1;
        for(int i=0;i<n;i++)
        {
    
    
            if(firt[i%k]=='?') firt[i%k]=s[i];//如果当前firt[i]仍然为'?"代表没有出现固定的0或者1
            else if(s[i]!='?'&&firt[i%k]!=s[i]) flag=0;
            //如果firt[i%k]已经是0或者1了,而s[i]不为'?'且不与firt[i%k]相同的话则出现冲突,直接flag置零
        }
        int sum0=0,sum1=0;
        for(int i=0;i<k;i++)//再检测,每段长度为k的子串中,目前已经确定必须有的0和1各自有多少个
        {
    
    
            if(firt[i]=='0') sum0++;
            if(firt[i]=='1') sum1++;
        }
        if(sum0>k/2||sum1>k/2) flag=0;//如果0或者1出现的个数超过了长度k的一半,同样是不满足条件的
        //此处与firt数组实现的条件一同,构成了题目要求的条件
        if(flag) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
}

D题
树上博弈

题意为在一个有n个结点的数上,Alice和Bob一开始分别在a和b点。从Alice开始,他们交题开始移动,如果能做到某一刻,Alice和Bob处在同一个位置上,那么Alice胜利,否则Bob胜利。Alice每次移动最远可以移动到距离当前位置da的点,Bob每次移动最远可以移动到距离当前位置db的点,现在希望你判断出两者谁胜出。

博弈么,我们同样的从最基础的情况开始考虑。

首先Alice如何在下一次移动后能获得胜利呢,也就是两者所处位置的距离dis_ab<=da的时候,可以一次移动直接走到Bob的位置上获得胜利。

之后我们先不要考虑在一颗树上,考虑在一个有L个结点的直线上,如果2 × \times ×da>=L-1的话,Alice可以先选择走到这条直线中间的位置,Alice的下一步移动可以走到这条直线上的任意一个位置。此时不论Bob在哪里,Alice都能胜利。
接着考虑在树上,树的话同样可以看若干直线交错重叠构成的,树的直径x即为最长的那一根直线的长度,只需要2 × \times ×da>=x,Alice就能采取上述的策略保证自己完胜。

那么如果2 × \times ×da<x,Alice无法通过上述策略获得胜利呢。
同样的我们仍然是在一条直线上去考虑,对于Bob来说他在这条直线上如果一直保持在Alice的同一侧的话,Alice可以通过不断减少该侧的长度,不断逼近,最后获得胜利。因此Bob一定要有从Alice的左侧到右侧(或者右侧到左侧),且保证自己移动后不会被Alice一次追上。
Bob与Alice的距离不能小于等于da,由此我们可以得到当2 × \times ×da>=db的时候,Bob是无法在确保自己安全的情况下,从Alice的一侧移动到另一侧的,此时Alice必胜。

那么2 × \times ×da<db的时候呢,此时Bob可以安全地做到从一侧移动到另一侧,此时Bob可以确保自己不败。

自此推导完成所有情况。

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const ll maxn=1e5+7;

ll n,a,b,da,db,dis_ab;//dis_ab记录在树上a和b两点之间的距离
ll dis[maxn];//dis[i]记录每次dfs过程中,i点距离起点的距离

struct Edge
{
    
    
    ll to,next;
}edge[maxn<<1];

ll head[maxn],tot;

void init()
{
    
    
    for(ll i=1;i<=n;i++) head[i]=-1;
    tot=0;
}

void add(ll u,ll v)
{
    
    
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
//前向星存图

void dfs(ll now,ll pre,ll deep)
{
    
    
    dis[now]=deep;
    for(ll i=head[now];i!=-1;i=edge[i].next)
    {
    
    
        ll to=edge[i].to;
        if(to!=pre)
        {
    
    
            dfs(to,now,deep+1);
        }
    }
}


int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        cin>>n>>a>>b>>da>>db;
        init();
        for(ll i=1;i<n;i++)
        {
    
    
            ll u,v;
            cin>>u>>v;
            add(u,v);
            add(v,u);
        }

        //两次dfs得到树的直径,第一次dfs的起点可以任意选择,第二次dfs的起点选择第一次dfs过程中距离最远的点
        //两次dfs得到的两个距离最远的点就是树的直径的两个端点,第二次dfs结束后dis[]数组中的最大值就是树的直径大小
        //由于第一次dfs的起点可以任意选择,因此我们选择a为起点,顺带计算出a和b两点间的距离
        dfs(a,-1,0);
        ll tar=1;
        for(ll i=2;i<=n;i++)
            if(dis[i]>dis[tar]) tar=i;

        dis_ab=dis[b];
        dfs(tar,-1,0);
        tar=1;
        for(ll i=2;i<=n;i++)
            if(dis[i]>dis[tar]) tar=i;

        if(dis_ab<=da||2*da>=dis[tar]||da*2>=db) cout<<"Alice"<<endl;
        else cout<<"Bob"<<endl;
    }
}

猜你喜欢

转载自blog.csdn.net/StandNotAlone/article/details/108474095