Codeforces Round #808 div2 ABCD

A

题意

给一个数组,定义一种操作使a[i] 变成a[i] - a[i - 1] ,问能否把除了a[1]以外的数删成0

分析

想要删成0,必然要保证每一个数都是a[1]的倍数,不然怎么可能删干净呢?

#include<bits/stdc++.h>
using namespace std;
int a[110];
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int n;
        cin>>n;
        for(int i=0;i<n;i++)
            cin>>a[i];
            
            int f=0;
            for(int i=1;i<n;i++)
                if(a[i]%a[0]!=0){f=1;break;}
                
            if(f==0)cout<<"YES"<<endl;
            else cout<<"NO"<<endl;
    }
    return 0;
}

B

题意

给定一个三个数,n,a,b,请问能否构造一个数组1 - n,使得每一个位置上的数a[i]与该位置的坐标 i 的gcd两两不相同,要求所有数在[a, b]的范围内,输出任意一个符合条件的数组,无解输出NO。

分析

众所周知一个gcd(x,y) <= min(x, y),而我们的i坐标是从1~n,因此每一个数要对应的gcd必须是坐标i本身,因此我们只需要判断[a, b]中有没有 i 的倍数即可。如何快速判断呢? 举个例子: 给定范围[11 , 14],请问是否有3的倍数? 我们计算大于11的第一个3的倍数 即 11/ 3 * 3,对11/ 3进行下取整,得到的数*3就是a的(最)小倍数。如果得到的数<a,那么只要多加几次+i就行,通项公式即 a / i * i ,最后判断求出的数数否 <= b 即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<ll>ans;
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        ll n,l,r;
        cin>>n>>l>>r;
        ans.clear();
        
            ans.push_back(l);
            for(ll i=2;i<=n;i++)
            {
                ll x=(l/i)*i;
                while(x<l)x+=i;
                if(x>=l&&x<=r)ans.push_back(x);
            }
            
            if(ans.size()==n){
                cout<<"YES"<<endl;
                 for(ll j=0;j<n;j++)
                cout<<ans[j]<<" ";
                cout<<endl;
            }else 
                 cout<<"NO"<<endl;
                 
    }
    return 0;
}

C

题意

有n场考试,每场考试有难度a[i], 你的智商为m,若智商>=a[i], 则接受考试不会掉智商,否则智商-1,请问如何安排考试(选择考与不考)使得你能够尽可能的参加更多的考试

题解

当我们遇到一个考试其难度a[i] <= m,我们直接拿下这场考试。
如果智商不够,我们则需要判断一下如果这一点智商消耗在这里会不会对未来很多的考试产生影响。
举一个例子:你的智商为1,你将要面对的考试是 2 1 1 1 1 ,如果你因为头铁干掉了第一场考试,你就会因为过于弱智痛失剩下的所有考试。然而我们希望的是每一点智商都不浪费。那么我们考虑什么时候要头铁。
假如剩余的智商能够将后面所有的考试全部参加了,那么我们就可以头铁的去参加考试。否则说明我们的智商不足以将所有考试参加完,但是假如我们不参加这次考试,我们就拥有更高的智商,这无疑对于后面的考试是有益的,也不会造成智商的浪费问题。
因此如果我们头铁了,说明我们能够干掉后面所有的考试,因此这是科学的头铁。
那么如何判断在 i 位置能否将后面所有的考试参加完呢?(何时头铁?)从后往前扫描,设dp[i] = 从 i 到 n 参加完所有的考试所需要的最少的智商,显然最坏的情况是i ~ n的考试数量,每场考试分配一点智商,但是我们发现如果==a[i]<=dp[i + 1]==这种情况就可以不分配智商,具体见代码。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],dp[N],ans[N];//dp[i],解决i~n考试所需的最低智商
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int n,q;
        cin>>n>>q;
        for(int i=1;i<=n;i++)cin>>a[i];
        dp[n]=1;//解决最后一个问题,1显然够用了
        for(int i=n-1;i>=1;i--){//dp[i + 1] 是从i + 1开始需要的最低智商,如果a[i]的难度<= dp[i + 1]那就说明此时dp[i + 1]的智商可以胜任a[i],因此dp[i] = dp[i + 1].
            if(a[i]<=dp[i+1])dp[i]=dp[i+1];//a[i]<=解决i+1~n考试所需要的最低智商,
            else dp[i]=dp[i+1]+1;//大于的话就要+1了
        }

        for(int i=1;i<=n;i++)
        {
            if(q>=a[i]){//无条件拿下
                    ans[i]=1;
                    continue;
            }
            if(q>=dp[i])//此时智商足够解决i~n的所有问题,开始头铁
            {
                for(int j=i;j<=n;j++)ans[j]=1;
                break;
            }
            ans[i]=0;//智商不够,也没达到能头铁的地步,只能放弃,明哲保身
        }

        for(int i=1;i<=n;i++)cout<<ans[i];
        cout<<endl;

    }
    return 0;
}

D

题意

给定一个长度为n的数组,每次a[i] 会变成 a[i + 1] - a[i],即做一次差分,第一个元素消失(即数组长度-1),然后排序。那么经过n - 1次操作,数组长度就剩1,求出这个数。

分析

如果暴力模拟的话时间复杂度是n^2logn,显然是会T的。那么如何优化这个过程呢?显然没有公式去解决这种问题,避免不了模拟。但是我们发现,随着差分的不断进行,0的数量显然会大大增加。2 5 8 10 13 17 22 28 – 差分 --> 3 3 2 3 4 5 6 – sort --> 2 3 3 3 4 5 6
2 3 3 3 4 5 6 – 差分 --> 1 0 0 0 1 1 1 – sort --> 0 0 0 1 1 1 1
0 0 1 0 0 0 – 差分 --> 0 0 0 0 1 此时已经发现结果最后必然是1了。 n为8但是我们只差分了3次

我们发现0的数量是会突然巨增的(多造几组样例试试),如何减少时间复杂度呢?忽略大量的0即可,我们只需要找到第一个非0的位置,在这个位置之后进行差分即可。每一次去模拟这个过程,所有位置要先左移一格,因为a[1]会不断删去,然后直接暴力遍历找非0位置,也可以二分(时间复杂度都允许,因为0的出现非常的多),记录非0位置,从这个位置开始往后做差分,若非0位置的数只剩一个了那么就可以停止循环。其实他的时间复杂度是远低于n^2logn的。注意在做差分的时候其实找的是最后一个0,因为这个0对后面的差分仍然具有贡献,看上面模拟的样例,这道题的边界问题比较复杂,我注释写的比较详细了。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int a[N];
int main()
{
    int t,p=0;//p记得初始化
    cin>>t;
    while(t--)
    {
        int n;cin>>n;
        for(int i=1;i<=n;i++)cin>>a[i];
 
        for(int i=1;i<=n;i++)
            if(a[i]){p=i-1;break;}//找到最初的最末尾的0(虽然叫末尾0,但却是前面的)
        
        while(n>1){
           for(int i=max(1,p);i<=n-1;i++)//注意p不能是0,最少是1,假如没有0的话p算出来就是0,出界了
                a[i]=a[i+1]-a[i];//差分一下
                n--;//长度缩短1
                sort(a+p,a+n+1);//排序一下
 
            //二分找末尾0
            int l=0,r=n;
            while(l<r)
            {
                int mid=l+r+1>>1;//二分,往左边靠
                 if(a[mid]) r=mid-1;
                 else l=mid;
            }
            p=l;//找到末尾0得位置
            if(l+1==n) break;//l+1是第一个非0位置,如果第一个非0是n那么说明只有一个数非0,直接退出循环即可。
        }
        cout << a[n] << endl;
 
    }
    return 0;
}
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值