【题解】Codeforces Round #600(Div.2)

Codeforces Round #600(Div.2)

https://codeforces.com/contest/1253/problem

A.Single Push

思路:数组各位相减,得到b-a之后的。如果全为0,或者只有一段非0且数字相同则可行,否则不可行。具体实现的话,可以左右两边指针向中间搜到第一个不为0的数,再判断中间是否均为同一个数。复杂度\(O(n)\)

注意:多组数据一定要判断是否需要清空。这里我a[n+1]没有清0,结果WA on test55……

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int M=1e5+20;
int a[M];
int main(){
    int T;
    scanf("%d",&T);
    for (int z=1;z<=T;++z){
        int n;
        scanf("%d",&n);
        for (int i=1;i<=n;++i)
            scanf("%d",&a[i]);
        a[n+1]=0;
        int c;
        for (int i=1;i<=n;++i){
            scanf("%d",&c);
            a[i]-=c;
        }
        bool v=true;
        int l=1,r=n;
        while(l<=n&&a[l]==0)
            ++l;
        while(r>l&&a[r]==0)
            --r;
        int std=a[l];
        if (std>0)
            v=false;
        else
            for (int i=l;i<=r;++i)
                if (a[i]!=std){
                    v=false;
                    break;
                }
        if (v)
            printf("YES\n");
        else
            printf("NO\n");
    }
    return 0;
} 

B.Silly Mistake

思路:可以用unordered_set记录目前在office里面的人,用unordered_map来记录一个人是否进入过office。对于每个event,如果为正,则判断一下是否之前已经进入过,如果已经进入过则非法,否则就记录这个人为1,并加入set;如果为负,则先判断一下是否在set里,如果没有则非法,否则就删掉这个人,这里再判断一下office是否空了,如果空了就直接当做一天结束(因为题目说天可以随便划分)。复杂度\(O(n)\)

注意:

  1. \(O(1)\)清空STL:直接建一个相同的,每次直接st=st0;
  2. 很多情况可以用unordered_set和unordered_map来优化复杂度(去掉log),不过小心被卡掉(哈希)。不过可以想办法保证不被卡掉,大概意思是自己写一个随机化的哈希,详情见neal的博客(cf上)。

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int M=2e6+20,P=1e6;
vector<int> vec;
unordered_set<int> st;
unordered_map<int,int> mp,mp0;
int c[M];
int main(){
    int n;
    scanf("%d",&n);
    bool v=true;
    for (int i=1;i<=n;++i)
        scanf("%d",&c[i]);
    for (int i=1;i<=n;++i){
        if (c[i]>0){
            if (mp[c[i]+P]){
                v=false;
                break;
            }
            else
                st.insert(c[i]),mp[c[i]+P]=1;
        }
        else{
            if (st.count(-c[i])){
                st.erase(-c[i]);
                if (st.empty())
                    vec.push_back(i),mp=mp0;
            }
            else{
                v=false;
                break;
            }
        }
    }
    if (!st.empty())
        v=false;
    if (!v)
        printf("-1\n");
    else{
        printf("%d\n%d",vec.size(),vec[0]);
        for(int i=1;i<vec.size();++i)
            printf(" %d",vec[i]-vec[i-1]);
    }
    return 0;
}

C.Sweets Eating

思路:首先贪心很容易想到,每次总是先吃\(a_i\)更大的糖,这样总penalty最少。但题目要求输出k=1-m的所有情况。举几个例子之后可以发现,只要从小到大排序,那么k每增加1,答案会加上排序后的\(a[k\%m],a[k\%m+m],a[k\%m+2m]……\),下标不大于k。那么只要先排个序,之后再预处理每个\(k\%m\)的前缀和,再扫一遍输出答案即可。复杂度\(O(nlogn)\)

注意:不开LL见祖宗。

AC代码:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int M=2e5+20;
int a[M];
vector<LL> mmd[M]; 
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i)
        scanf("%d",&a[i]); 
    sort(a+1,a+n+1);
    for(int i=0;i<m;++i){
        int j=1;
        mmd[i].push_back(a[i]);
        while(j*m+i<=n)
            mmd[i].push_back(mmd[i][j-1]+a[j*m+i]),++j;
    }
    LL ans=a[1];
    printf("%lld",ans);
    for (int i=2;i<=n;++i){
        ans+=mmd[i%m][i/m];
        printf(" %lld",ans);
    }
    putchar('\n');
    return 0;
}

D.Harmonious Graph

思路:这道题可以发现,题目的描述等价于:如果l到r有一条路径,那么l,r之间所有点都必须是连通的。那么我们可以想到用并查集(dsu)来处理。

首先遍历每一条边进行合并,之后再扫一遍将每个集合找出来,记录每个集合的元素个数cnt,最小点序号mn和最大点序号mx。

如果mx-mn+1=cnt,那么说明这个集合中的元素为mn\(\sim\)mx,满足了题意条件。

如果mx-mn+1<cnt,那么说明有些点在其他的集合里,我们必须要将这两个集合连起来才可以把缺失的点放进来。那么连接两个集合只需要添加一条边,直接答案加1即可。

当然,连接完之后的集合变大了,可能仍然不满足mx-mn+1=cnt,那么就继续上述步骤直到满足了该条件为止。当所有的集合都满足条件之后,就输出答案即可。

那么怎么来找到缺失的点所在的集合呢?我们对每个并查集只记录cnt,mn,mx,将所有并查集按照mn从小到大排序。若第i个集合不满足条件,那么肯定会缺失第i+1个集合的mn点,就将i和i+1集合合并,如果不满足就继续合并,直到满足了为止。这可以用优先队列来实现。

复杂度为并查集和优先队列的\(O(nlogn)\),实现常数有点大,不过140ms过掉了。

注意:好像有\(O(n+m)\)的做法……回头看Tutorial吧。

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int M=2e5+20;
int road[M];
int fnd(int x){
    return (x==road[x])?x:road[x]=fnd(road[x]);
}
void merge(int u,int v){
    road[fnd(u)]=fnd(v);
}
struct Bcj{
    int mn,mx,cnt;
    Bcj(int a=1e9+7,int b=0,int c=0):mn(a),mx(b),cnt(c){}
    void proc(int i){
        mn=min(mn,i);
        mx=max(mx,i);
        ++cnt;
    }
    void clear(){
        mn=1e9+7;
        mx=cnt=0;
    }
    void merg(Bcj x){
        mn=min(mn,x.mn);
        mx=max(mx,x.mx);
        cnt+=x.cnt;
    }
}bcj[M];
struct cmp{
    bool operator()(Bcj x,Bcj y){
        return x.mn>y.mn;
    }
};
priority_queue<Bcj,vector<Bcj>,cmp> q;
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    int a,b;
    for (int i=1;i<=n;++i)
        road[i]=i;
    for (int i=1;i<=m;++i){
        scanf("%d%d",&a,&b);
        merge(a,b);
    }
    for(int i=1;i<=n;++i)
        bcj[i].mx=bcj[i].mn=i,bcj[i].cnt=1;
    for(int i=1;i<=n;++i)
        if (i!=fnd(i)&&bcj[i].mx)
            bcj[fnd(i)].proc(i),bcj[i].clear();
    //for(int i=1;i<=n;++i)
    //  cout<<bcj[i].mn<<' '<<bcj[i].mx<<bcj[i].cnt<<endl;
    for(int i=1;i<=n;++i)
        if (bcj[i].mx)
            /*cout<<bcj[i].mn<<' '<<bcj[i].mx<<' '<<bcj[i].cnt<<endl,*/q.push(bcj[i]);
    Bcj prs;
    int ans=0;
    while(!q.empty()){
        Bcj prs=q.top();
        q.pop();
        while(prs.cnt!=prs.mx-prs.mn+1){
            prs.merg(q.top());
            q.pop();
            ++ans;
        }
    }
    printf("%d\n",ans);
    return 0;
}

E.Antenna Coverage

题意:一条街道m个点[1,m],有n个发射站,每个发射站有两个参数\(x_i\)\(s_i\),表示发射站位于\(x_i\),能覆盖\([x_i-s_i,s_i+s_i]\)。我们可以花1coin将每个发射站的\(s_i\)增加1,问最少花费多少coins能够完全覆盖[1,m]。保证两个发射站不会位于同一个地点。\(1\leq n\leq 80\)\(n\leq m\leq 100000\)

思路:VP的时候看数据范围猜到了应该是dp,但没想到怎么转移。实际上应该不算很难想。设dp[i]表示覆盖[1,i]的最小花费,那么转移方式有两种:

  1. 从[1,i-1]的方案直接转移过来。如果i处已经被覆盖了,那么dp[i]=dp[i-1];如果i处没有被覆盖,那么就让覆盖了i-1的那个基站再扩大1,有dp[i]=dp[i-1]+1。
  2. 从右端点<=i的基站转移过来。有dp[i]=min(dp[i],i-ant[j].r+dp[max(2*ant[j].x-i-1,0)])。其中ant[j]表示第j个基站,r表示基站的\(x_j+s_j\)。这一步要枚举所有符合条件的基站,转移复杂度为\(O(n)\)

答案为dp[m],判断是否被覆盖需要预先对[1,m]的区间建立vis数组预处理,最坏复杂度是\(O(nm)\),dp复杂度也是\(O(mn)\),所以总复杂度是\(O(nm)\)

注意:

  1. 要增加一个(x=0,s=0)的基站。由于用这个基站去覆盖任何区间的方案都是最差的,因此不会影响答案(每次都取最小值)。
  2. 注意预处理和dp的时候不要越界,左侧最小是0。

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int M=1e5+20;
struct Ant{
    int x,s,l,r;
    Ant(int a=0,int b=0,int c=0,int d=0):x(a),s(b),l(c),r(d){}
    bool operator<(Ant x){
        return r<x.r;
    }
}ant[100];
int dp[M],vis[M];
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i){
        int x,s;
        scanf("%d%d",&x,&s);
        ant[i]=Ant(x,s,x-s,x+s);
        for(int j=max(0,x-s);j<=min(m,x+s);++j)
            vis[j]=1;
    }
    dp[0]=0;
    for (int i=1;i<=m;++i){
        if (vis[i])
            dp[i]=dp[i-1];
        else
            dp[i]=dp[i-1]+1;
        for (int j=1;j<=n;++j)
            if (ant[j].r<=i)
                dp[i]=min(dp[i],i-ant[j].r+dp[max(2*ant[j].x-i-1,0)]);
    }
    printf("%d\n",dp[m]);
    return 0;
}

总结

这次把D题做出来了,虽然ABC代码实现和大佬们差不多,但感觉前面写的老有问题,写的有点慢,而且ABC各WA一发,说明写代码的风格和习惯还是不太好,D题想的比较快,表扬自己。E题没想出来其实不太应该,不过当时也是不太想写了。明晚cf争取上1800。

猜你喜欢

转载自www.cnblogs.com/diorvh/p/11886557.html