Codeforces Round #702 (Div. 3)A-G题解

Codeforces Round #702 (Div. 3)A-G题解
比赛链接:https://codeforces.ml/contest/1490

这场F读错题意白给一发,G二分的if(dp[mid]

Div3相对以前变简单了,罚时手速场。

A题
暴力

题意:
1000组数据
给定一个只包含正整数的长度为n的数组(n最大50),每个数字均在1到50之间。
你可以在任意位置插入任意的整数(这里其实有bug,你如果插入一个负数那怎么都满足了,题目要求的应该是正整数)。
问最少需要插入多少个数字,使得该数组每一对相邻的两个数字,都满足大的那个不超过小的那个的2倍。

思路:
数值都非常小,直接暴力检测每一对相邻的数字,贪心对小的那个数不断乘2翻倍直到大于等于大的那个数即可。

#include
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+7;
const double eps=1e-6;
const int mod=1e9+7;

int main()
{
     
    IOS
    int t;cin>>t;
    while(t--)
    {
     
        int n;cin>>n;
        vector<int>num(n);
        for(int i=0;i<n;i++) cin>>num[i];
        int ans=0;
        for(int i=1;i<n;i++)
        {
     
            int x=min(num[i],num[i-1]);
            int y=max(num[i],num[i-1]);
            while(x*2<y)
            {
     
                ans++;
                x*=2;
            }
        }
        cout<<ans<<endl;
    }
}

B题
暴力

题意:
给定一个长度为n的只包含整数的数列,其中n必定能被3整除。
数列中对3取余得到0的数字的个数记为cas0,
数列中对3取余得到1的数字的个数记为cas1,
数列中对3取余得到2的数字的个数记为cas2,
每次操作,你可以将数列中的某个数字的值+1。
问最少经过多少次操作,可以使得cas0=cas1=cas2。

思路:
我们最后的目标是使得cas0=cas1=cas2=n/3。
那么实际上我们就是要把多了的通过+1操作补到少了的cas上去。

如果cas1大于n/3的话,我们可以把多了的部分都+1,让他们变成cas2的值。

cas0,cas1,cas2的具体情况当然是有很多种,但是不论是哪种,都存在[0->1->2]或者[1->2->0]或者[2->0->1]这样的转移顺序使得最后三个cas都为n/3。
直接暴力for两遍0-2的转移即可。

#include
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+7;
const double eps=1e-6;
const int mod=1e9+7;

int main()
{
     
    IOS
    int t;cin>>t;
    while(t--)
    {
     
        int n;cin>>n;
        vector<int>num(n);
        int cas[3]={
     0};
        for(int i=0;i<n;i++)
        {
     
            cin>>num[i];
            if(num[i]%3==0) cas[0]++;
            else if(num[i]%3==1) cas[1]++;
            else cas[2]++;
        }
        int ans=0;
        for(int i=0;i<2;i++)//循环两遍
         for(int j=0;j<3;j++)
            if(cas[j]>n/3) {
     ans+=cas[j]-n/3;cas[(j+1)%3]+=cas[j]-n/3;cas[j]=n/3;}
        cout<<ans<<endl;
    }
}

C题
预处理,map

题意:
t组数据,最大100组。
每组数据给定一个1e12以内的正整数x,你需要求出满足a3+b3=x的(a,b)有多少对。

思路:
注意到x最大为1e12,那么a和b的取值最大只有1e4。
我们可以预处理出1到1e4这1e4个整数的三次方为多少,用map记录其是否出现。
每组数据,我们暴力枚举这1e4个三次方,用map检测x减去当前的值,剩下的部分是否也为一个立方数即可。
复杂度为100 × \times × 1e4 × \times × log(1e4)。

#include
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+7;
const double eps=1e-6;
const int mod=1e9+7;

map<ll,int>M;
vector<ll>num;

int main()
{
     
    IOS
    int t;cin>>t;
    for(int i=1;i<=10000;i++)
    {
     
        ll x=i;
        x=x*x*x;
        M[x]=1;
        num.push_back(x);
    }
    while(t--)
    {
     
        bool flag=0;
        ll x;cin>>x;
        for(int i=0;i<num.size();i++)
        {
     
            if(M.find(x-num[i])!=M.end()) {
     flag=1;break;}
        }
        if(flag) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
}

D题
暴力模拟,dfs

题意:
给定一个长度为n的排列。
你要按照如下的规则将这个排列构造成一棵二叉树,依次输出每个位置在这棵树中的深度是多少。

先把整个排列中最大的数,作为树根。
在原排列中,在当前这个数左侧的所有数都在当前根的左子树中,并且以其中最大的值为子树的根。
在原排列中,在当前这个数右侧的所有数都在当前根的右子树中,并且以其中最大的值为子树的根。
之后左右子树同上进行构造。

思路:
直接dfs模拟这个过程就可以了,分析复杂度会发现最糟糕也就是n2,再乘以数据组数100组也就1e6的级别。

#include
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+7;
const double eps=1e-6;
const int mod=1e9+7;

int num[107];
int deep[107];//deep[i]记录第i个数在树中所在的深度

void dfs(int l,int r,int now,int d)//l和r记录当前子树的值在原排列中的下标区间,now为当前作为根的数是哪一个,d代表深度
{
     
    if(l==r) return;
    if(l<now)//处理左区间
    {
     
        int tar=now-1;
        for(int i=l;i<now-1;i++) if(num[i]>num[tar]) tar=i;
        deep[tar]=d+1;
        dfs(l,now-1,tar,d+1);
    }
    if(r>now)//处理右区间
    {
     
        int tar=now+1;
        for(int i=now+2;i<=r;i++) if(num[i]>num[tar]) tar=i;
        deep[tar]=d+1;
        dfs(now+1,r,tar,d+1);
    }
}

int main()
{
     
    IOS
    int t;cin>>t;
    while(t--)
    {
     
        int n;cin>>n;
        for(int i=1;i<=n;i++) cin>>num[i];
        memset(deep,0,sizeof(deep));
        int tar=1;
        for(int i=2;i<=n;i++) if(num[i]>num[tar]) tar=i;
        deep[tar]=0;
        dfs(1,n,tar,0);
        for(int i=1;i<=n;i++) cout<<deep[i]<<' ';
        cout<<endl;
    }
}

E题
前缀和,贪心
真是绝了,上场读错了的题意这次居然真的出了这样的题

题意:
给定n个玩家,每个玩家一开始都有若干的代币。
会进行n-1次比拼,每次比拼随机选出两个代币数量不为0的玩家。
如果两个人代币数量不相同,代币多的那个人,拿走两个人的全部代币。
如果两个人代币数量相同,在这两个人中随机选一个人,拿走两个人的所有代币。
最后只剩下一个人。

问有哪些人是有可能是最后剩下的那个人。

思路:
既然说是有可能了,那么我们可以理解为我们可以贪心地去操作比赛的结果,平局的时候我们都令自己选的哪个人获胜。
对于每个人来说,肯定都是先贪心去找其他人当中代币最少的那个去比,如果出现剩下的人中,代币最少的那个也比自己的代币多,那就没办法胜利了。

实际上我们注意到,胜利的人会拿走所有的代币。我们先对着n个人按照代币数量从小到大排序为num[n]数组。随便取一个人,如果他打赢了前i个人的话,那么它当前的代币数量就是num[i]的前缀和。
如果num[i]

我们从后往前去找,找到前缀和不足以赢下一个人的位置,就得到了想赢所有人需要的最少代币。输出初始代币数量不少于这个值的人即可。

#include
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=2e5+7;
const double eps=1e-6;
const int mod=1e9+7;

ll num[maxn],chuli[maxn],sum[maxn];

int main()
{
     
    IOS
    int t;cin>>t;
    while(t--)
    {
     
        int n;cin>>n;
        for(int i=1;i<=n;i++)
        {
     
            cin>>num[i];
            chuli[i]=num[i];
        }
        sort(chuli+1,chuli+n+1);
        for(int i=1;i<=n;i++) sum[i]=sum[i-1]+chuli[i];//sum为前缀和数组
        ll bas=chuli[n];//bas记录有可能获胜的人的初始代币最少为多少
        for(int i=n-1;i>=1;i--)
        {
     
            if(sum[i]<chuli[i+1]) break;//如果当前这个人把不比他大的所有人都赢了,此时他已经是剩下的人当中最少的人了
            //但是他如果赢不了其他人当中最小的那个,那他就没有获胜的希望
            bas=chuli[i];
        }
        vector<int>out;
        for(int i=1;i<=n;i++)
            if(num[i]>=bas) out.push_back(i);
        cout<<out.size()<<endl;
        for(int i=0;i<out.size();i++) cout<<out[i]<<' ';
        cout<<endl;
    }
}

F题
前缀和,暴力
读错题意成既可以删除也可以增加可还行,不过还好只需要删改两三行就可以了

题意:
给定一个长度为n只包含正整数的数组。(n最大2e5)
现在你需要删除尽可能少的数,使得数组中的每个数字出现的次数相同。

思路:
直接暴力枚举最后结果的数组中,每个数字出现了多少次,次数肯定在1到2e5之间。
而每次枚举的计算过程,可以利用前缀和来O(1)实现,具体看代码和注释。

#include
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=2e5+7;
const double eps=1e-6;
const int mod=1e9+7;

map<int,int>M;

ll cishu[maxn];//cishu[i]记录出现了i次的数字有多少种
ll sumcishu[maxn];//sumcishu为cishu数组的前缀和,记录出现了不超过i次的数字有多少种
ll num[maxn];//num[i]记录出现了i次数字,他们总共出现了几次
ll sumnum[maxn];//sunnum为num数组的前缀和,记录出现了不超过i次的数字,他们总共出现了几次

int main()
{
     
    IOS
    int t;cin>>t;
    while(t--)
    {
     
        int n;cin>>n;
        ll ans=llINF;

        for(int i=0;i<n;i++)
        {
     
            int x;cin>>x;
            if(M.find(x)==M.end()) M[x]=1;
            else M[x]++;
        }
        for(auto &x:M)
        {
     
            cishu[x.second]++;
            num[x.second]+=x.second;
        }

        for(int i=1;i<=n;i++)
        {
     
            sumcishu[i]=sumcishu[i-1]+cishu[i];
            sumnum[i]=sumnum[i-1]+num[i];
        }

        for(int i=1;i<=n;i++)//i为我们最后希望数组中剩下的数字,每个数字出现多少次
        {
     
            ll temp=0;
            temp+=n-sumnum[i]-(sumcishu[n]-sumcishu[i])*i;//对于一开始出现次数大于i次的,我们要把他们减少到i次
            //sumcishu[n]-sumcishu[i]即为有多少种数字一开始出现次数是大于i次的,他们最后都要变为出现i次,总次数就乘以i
            //用n-sumnum[i]即为一开始出现次数大于i次的总共出现了几次,减去目标的总次数即为需要删掉的个数
            temp+=sumnum[i-1];//出现次数小于i的所有数字都要被删掉
            ans=min(ans,temp);
        }

        cout<<ans<<endl;

        for(int i=1;i<=n;i++) cishu[i]=sumcishu[i]=num[i]=sumnum[i]=0;
        M.clear();
    }
}

G题
二分,前缀和,dp
二分函数的mid写成了l是我没想到的,疯狂白给5发,二分居然写错了,青结

题意:
给定n个数字,每次操作都从第一个数字开始,每过一秒,依次加上下一位置的数字,不断循环。

给定m个数字x,问经过多少秒之后,累加的和不小于x。如果永远不可能达到x,输出-1。
n和m最大均为2e5。

思路:
首先用sum[i]记录前i个数字累加起来是多少,mi记录sum数组的最大值。
如果给定的x小于等于mi的话,那么在第一轮循环遍n个数字前便已经结束。我们可以对sum[i]做一个dp,用dp[i]记录sum[1]到sum[i]中最大的值为多少。由此利用二分可在logn的时间找到累加大于等于x的第一个位置。

如果给定的x大于mi的话,就要看后面多次循环后能否达到了。x大于mi,那么x的值至少要被减去x-mi才行。
如果sum[n]<=0的话,代表每次循环结束x的值x与mi的差值并不会减少,因此此时就是无限循环,输出-1即可。
当sum[n]>0时,x-mi至少需要(x-mi)/sum[n]向上取整的循环后才会被减去,再下一轮循环就能达到值x了,同样利用二分来logn时间寻找位置。

#include
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=2e5+7;
const double eps=1e-6;
const int mod=1e9+7;

ll num[maxn];
ll sum[maxn];//num数组的前缀和
ll dp[maxn];//dp[i]记录sum[1]到sum[i]中最大的值为多少

int search(int n,ll x)
{
     
    int l=1,r=n;
    while(l<r)
    {
     
        int mid=(l+r)>>1;
        if(dp[mid]<x) l=mid+1;
        else r=mid;
    }
    return l;
}

int main()
{
     
    IOS
    int t;cin>>t;
    while(t--)
    {
     
        int n,m;cin>>n>>m;
        ll mi=-1e10;//记录最大的前缀和为多少,也就是依次循环过程中可以得到的最大数值
        for(int i=1;i<=n;i++)
        {
     
            cin>>num[i];
            sum[i]=sum[i-1]+num[i];
            dp[i]=max(dp[i-1],sum[i]);
            mi=max(mi,sum[i]);
        }
        while(m--)
        {
     
            ll x;cin>>x;
            if(x>mi)
            {
     
                if(sum[n]<=0) cout<<-1<<' ';
                else
                {
     
                    ll cas=(x-mi)/sum[n];
                    if((x-mi)%sum[n]) cas++;
                    x-=cas*sum[n];

                    cout<<cas*n-1+search(n,x)<<' ';
                }
            }
            else cout<<search(n,x)-1<<' ';
        }
        cout<<endl;
        for(int i=1;i<=n;i++) num[i]=sum[i]=dp[i]=0;
    }
}

你可能感兴趣的:(codeforces,算法)