CF973div2 B-E题解

一.B

题目链接:https://codeforces.com/contest/2013/problem/B

我先考虑一个简单的问题:对于a1 a2 a3,他的答案是什么呢?考虑a2最后被合并,

先合并a1,那么a2=a2-a1,再合并a3,a3=a3-a2+a1;

考虑a1最后被合并

先合并a3,a3=a3-a2,再合并a1,a3=a3-a2-a1;

很显然第一种方式更大。现在扩展问题,先考虑加一个数a4;

a3最后被合并,那么式子为a4=a4-([1,3]的合并最小),a4=a4-a3+a2+a1

a2最后被合并,那么a4=a4-a3-a2+a1

a1最后被合并,a4=a4-a3+a2-a1

考虑a1,a2,a3...an,假设我们选取ai作为分割点,ai最后合并,对于[1,i]而言,由于ai最后合并所以希望[1,i]合并后ai最小,可以知道ai最小为ai=ai-sum(a[1,i-1]),那么[i+1,n]需要最大,那么an必然需要减去a_{n-1},因为n-1的下标作为减号必然是被更高的标号支配,由于n-1后面只有一个n的下标,那么[i+1,n]的最大值上界为an-a_{n-1}+sum(a[i+1,n-2]),于是最后的ai的答案为an=an-a_{n-1}-ai+(sum-a_{n-1}-ai);

于是贪心的思考,分割点选择n-1最为合适

代码如下:

#include <bits/stdc++.h>

using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using namespace std;

void solve() {
    int n;
    cin>>n;
    i64 res=0,sum=0;
    i64 mm=0;
    for(int i=0; i<n; i++){
        int t;
        cin>>t;
        if(i<n-2) sum+=t;
        if(i==n-2)mm=t;
        if(i==n-1)res=t-(mm-sum);
    }
    cout<<res<<endl;
    
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);


    int t;
    cin >> t;

    while (t--) {
        solve();
    }

    return 0;
}

二.C

题目链接:https://codeforces.com/contest/2013/problem/C

思路:考虑先往一边加0,1判断,如果两者都不可,同时长度还未达到要求,考虑向另外一边加0,1;注意一个优化,当到另外一边后,查询次数应该是线性的,非0即1;

否则被下面一种情况hack,假设a+b=n,(换边的话b至少为1)

首先一边的01判断为2*(a+1),那么另外一遍的判断应该小于等于2*(b-1);

但是不优化的化判断为2*b,优化后为b

2*a+2+b,似乎还是会被hack,但是需要注意到一个问题,如果串包含01,第一次查询百分百正确,那么最坏情况为2*a+1+b<=2*n;如果串为0串或者1串,那么必然没有换边的情况,2*a<=2*n;

代码如下:

#include <bits/stdc++.h>

using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using namespace std;

void solve() {
    int n;
    cin >> n;
    auto ask = [&](string s) -> int {
        cout << "? " << s<< endl;
        cout.flush();
        int res;
        cin >> res;
        return res;
    };
        string s="";
        int cnt=n;
        char p1[4]={'0','1','1','0'};
        bool flag=false;
        while(cnt>0 ){
          if(!flag){
             int num=0;
             bool flag1=false;
            for(int i=0; i<=1; i++){
                string temp=s;
                temp=p1[i]+temp;
                if(ask(temp)==1){
                    cnt--;
                    s=temp;
                    num--;
                    flag1=true;
                }
                if(flag1)break;
                num++;
            }
            if(num==2)flag=true;
          }else{
               bool flag1=false;
                string temp=s;
                temp=temp+p1[0];
                if(ask(temp)==1){
                    cnt--;
                    s=temp;
                }
                else{
                    cnt--;
                    s=s+p1[1];
                }
             }
           
        }
         cout<<"! ";
         cout<<s<<endl;
        
    
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;

    while (t--) {
        solve();
    }
    return 0;
}

三.D

题目链接:https://codeforces.com/contest/2013/problem/D

有两种做法:

1.二分最大值和最小值

2.前缀平均求最小,后缀平均求最大

第一种做法:

考虑二分最小值的话,可以知道,mid比答案大时,比mid大的也false;比答案小时,还可以提高;check的标准是,前面的值传递到后面是否可以使得最小值提高

考虑二分最大值的话,可以知道,mid比答案大时,比mid大的也一定合法;

比答案小时,一定不合法。check的标准是,向后面传递时,把高于mid的高度,降低的值向后面传递使用,注意不要过量使用,同时这一定不会破坏最小值的条件,因为最小值是需要被补偿的那个。

代码如下:

#include <bits/stdc++.h>

using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using namespace std;

void solve() {
    int n;
    cin >> n;


        vector<i64> a(n);
        i64 maxnum = 0;
        i64 minnum = 1e13 + 10;

        for (int i = 0; i < n; i++) {
            cin >> a[i];
            maxnum = max(maxnum, a[i]);
            minnum = min(minnum, a[i]);
        }
        
        auto check = [&](i64 mid) -> bool {

            i64 ss=0;
            for (int i = 0; i < n ; i++) {
                if(a[i]<mid){
                    if(a[i]+ss<mid)return false;
                    else {
                        ss-=(mid-a[i]);
                    }
                }else ss+=a[i]-mid;
            }
            return true;
        };
        i64 l = minnum, r = maxnum;
        while (l < r) {
            i64 mid = (l + r+1 ) >> 1;
            if (check(mid)) {
                l = mid ;
            } else {
                r = mid-1;
            }
        }

   
        auto check1 = [&](i64 mid) -> bool {

            i64 ss=0;
            for (int i = 0; i < n - 1; i++) {
                if(a[i]>mid){
                    ss+=a[i]-mid;
                }
                if(a[i]<mid)ss-=min(mid-a[i],ss);
            }
            if(ss+a[n-1]>mid)return false;
            return true;
        };
        i64 ll=l,rr=maxnum;
        while(ll<rr){
            i64 mid = (ll + rr) >> 1;
            if (check1(mid)) {
                rr = mid ;
            } else {
                ll = mid+1;
            }
        }
       // cout<<l<<' '<<ll<<endl;
        cout<<ll-l<<endl;
        
        
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;

    while (t--) {
        solve();
    }

    return 0;
}

第二种做法:

前缀大概的思想是,考虑第i个数x,如果前面的数都小于x,那么最大的最小值在前面产生。如果存在一个数大于x,就可以通过运算,将x增加,这个增加应该是一种平均值。后缀的大致思想是,将运算考虑为i为+1,i-1为-1.对于第i个数x,后面的数如果都大于x,那么最大的最小值一定在后面产生,如果存在数y小于x,那么可以通过运算将y减少,同样也是平均值(但是求最大的最小值需要考虑平均值,差值为1的情况。)

代码如下:

#include <bits/stdc++.h>

using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using namespace std;

void solve() {
    int n;
    cin >> n;
    vector<i64> a(n);
    i64 maxnum=0,minnum=(i64)1e14+10;
    for(int i=0; i<n; i++){
        cin>>a[i];
    }
    i64 sum=0;
    for(int i=0; i<n; i++){
        sum+=a[i];
        minnum=min(minnum,sum/(i+1));
    }
    sum=0;
      for(int i=n-1; i>=0; i--){
        sum+=a[i];
        maxnum=max(maxnum,(sum+n-i-1)/(n-i));
    }
    cout<<maxnum-minnum<<endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;

    while (t--) {
        solve();
    }

    return 0;
}

四.E

题目链接:https://codeforces.com/contest/2013/problem/E

思路:实际上我们如果知道gcd(a)最多降低log(amax)次就可以很好的解决问题;

由于最多降低log(amax)次,我们考虑每次先寻找最小的gcd,直到不再降低;

为什么贪心是对的呢,考虑a,b,c,d,e这个序列(e<a),那么

序列1:a+gcd(a,b)+gcd(a,b,c)+gcd(a,b,c,d)+gcd(a,b,c,d,e)

序列2:e+gcd(e,a)+gcd(e,a,b)+gcd(e,a,b,c)+gcd(e,a,b,c,d)

显然序列2优于序列1:

e+gcd(e,a)<=a

gcd(e,a,b)<=gcd(a,b)

gcd(e,a,b,c)<=gcd(a,b,c)

那么可以知道,不论序列1如何变化,e始终放在第一位,那么我们于是可以更新e后面的数为gcd(x,min),同样的,我们只需要对gcd(x, min)求一下最小值,放在次位即可。这样就解决了排序问题。不会存在贪心的正确性问题,我们的正确性实际上是最优解的考虑,而非局部最优解的考虑。

代码如下:

#include <bits/stdc++.h>

using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using namespace std;

void solve() {
    int n;
    cin >> n;
    vector<int> a(n);
    for(int i=0; i<n; i++)cin>>a[i];
    int g=0;
    i64 ans=0;
    int cnt=n;
    while(true){
        int ming=1e8+10;
        for(int i=0; i<n; i++){
            int x=gcd(g,a[i]);
            ming=min(ming,x);
        }
        if(ming==g)break;
        g=ming;
        ans+=ming;
        cnt--;
    }
    cout<<ans+(i64)g*cnt<<endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;

    while (t--) {
        solve();
    }

    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值