• Codeforces Round #768 (Div. 2)思路分享


    Codeforces Round #768 (Div. 2)

    又是卡在D上面了....之后直接跳过去做E还做假了,血亏,当然,肯定是又掉分了....

    A. Min Max Swapr

    考虑a,b中的最大的数一定是会被max出来,考虑让另一个数小,那么直接将相同位置下大的数都放到一个序列中,小的数放到另一个序列中,保证了另一个数足够小即可。

    B. Fun with Even Subarrays

    首先可以发现由于都是让前面的数变成后面的数,所以最后一个数是没法改变的。那么也就是说我们只需要考虑将整个序列全部变成最后一个数即可。发现这个操作就是一个倍增的操作,直接枚举就行。同时倍增之后,我们还需要将原本序列中与最后一个数相等的数联合起来即可。

    点击查看代码
    #include<bits/stdc++.h>
    using namespace std;
    const int N=2e5+10;
    int T,n,a[N];
    int main()
    {
    //    freopen("1.in","r",stdin);
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d",&n);
            for(int i=1;i<=n;++i) scanf("%d",&a[i]);
            int j=n;
            while(j>1&&a[j-1]==a[j]) --j;
            if(j==1) {puts("0");continue;}
            for(int i=1;i<=20;++i)
            {
                if((n-j+1)*2>n) {printf("%d\n",i);break;}
                j=n-2*(n-j+1)+1;
                a[j]=a[n];
                while(j>1&&a[j-1]==a[j]) --j;
                if(j==1) {printf("%d\n",i);break;}
            }
        }
        return 0;
    } 
    
    C. And Matching

    (像这种构造题,想到啥就直接莽就对了....)
    考虑他给的数字,0,1,...,2^k-1,这样的数对于二进制下,就是0,1,10,11,.....,11111.11都凑齐了。首先可以想到的就是如果k是0的话我们完全可以搞,每个数和他对应的取反为一组,这样的话和就是0。(也就是x与n-1-x一组).同时我们发现n-1这个数字很特殊,因为任何数和它相与答案都是本身。那么我们考虑0<=k<n-1的情况,我们可以先让k与n-1一组,得到k,之后剩下的两两组合,使得每一组的答案都是0.如果不想动脑筋的话,可以直接爆搜,发现越小的数越优,就从大到小搜即可。(我比赛时就是这样考虑的。)可现在发现完全没有必要。我们发现在原先的配对中是k与n-1-k一组,0与n-1一组,这里我们是让k与n-1一组,那么让0与n-1-k一组,他们值也为0,剩下的还是按照刚才的准则即可。到这就完了吗?不要完忘了k=n-1的情况。按照刚才的策略,我们考虑怎么快速的凑出来n-1,由于n-1与任何数一组都不是n-1,所以我们选择n-1与n-2一组,这样的话值为n-2,还差1,剩下一个1,怎么凑都行,我是想着优先消耗大数,就指定n-3与一个数与起来为1.剩下的继续爆搜即可。但考虑一下确定性的做法,(爆搜终究不是正道,虽然能过...),还是考虑他们原本的配对,现在剩下的是0,1,我们考虑让1,3相与他们的值是1,那么剩下的就是0,n-4,他们相与的值是0,剩下的还是按照原本的策略考虑即可。
    这里就放下我爆搜的代码吧。

    点击查看代码
    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+10;
    int n,vis[N],T,k;
    vector<pair<int,int> >ve;
    int main()
    {
    //    freopen("1.in","r",stdin);
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d",&n,&k);
            for(int i=0;i<n;++i) vis[i]=0;
            ve.clear();
            if(k==n-1)
            {
                vis[n-2]=1;vis[n-1]=1;
                ve.push_back({n-2,n-1});
                for(int i=n-4;i>=0;--i) if((i&(n-3))==1) 
                {
                    vis[i]=1;
                    vis[n-3]=1;
                    ve.push_back({i,n-3});
                    break;
                }
                if(!vis[n-3]) {puts("-1");continue;}
            }
            else
            {
                vis[k]=1;vis[n-1]=1;
                ve.push_back({k,n-1});
            }
            bool flag=true;
            for(int i=n-1;i>=0;--i)
            {
                if(vis[i]) continue;
                for(int j=i-1;j>=0;--j) if(!vis[j]&&(i&j)==0)
                {
                    vis[j]=1;vis[i]=1;
                    ve.push_back({i,j});
                    break;
                }
                if(!vis[i]) {flag=false;break;} 
            }
            if(!flag) {puts("-1");continue;}
            for(auto x:ve) printf("%d %d\n",x.first,x.second);  
        }
        return 0;
    } 
    
    D. Range and Partition

    又是一道劝退题,当时写到D时还有一个小时,真的是吓到了...(人真的是不能怂啊...)
    首先我们考虑如果规定值域[x,y]的情况下,我们怎么划分区间。
    比较直观的想法就是我们直接从左到右扫一遍,一旦当前的区间内,值域内的数字大于非值域内数字时,我们就在当前区间为划分。虽然可能划分到最后可能区间不合法,可我们可以考虑合并,因为任意一个合法的区间都是值域内的数字大于非值域内的数字,所以我们将一个合法的区间并入不合法到区间只会使得它更可能合法。按照这种划分方法的话,可以发现每个划分区间内的值域内的数-非值域内的数都为1.这样的话,就得保证这个序列在值域内的数字的个数p-q不在值域内的数字个数>=k.那我们反过来,对不对?就是只要保证了上述条件,一定会有合法的划分的方案。考虑我们每个合法区间的p-q=1.所以我们能够划分的区间数就是整个序列的p-q。只要保证了这个,我们划分的区间个数就一定>=k。如果>k的话,我们直接合并区间即可。
    现在我们知道了给定值域下什么情况是可以的,什么情况不可以。我们可以枚举x,对于y而言,发现y越大,显然p是越多的,也更满足题意。可以二分,二分y用前缀和快速判断即可。

    点击查看代码
    #include<bits/stdc++.h>
    using namespace std;
    const int N=2e5+10;
    int T,n,k,a[N],sum[N],num;   
    pair<int,int>b[N];
    inline bool check(int x,int y)
    {
        int d=sum[y]-sum[x-1];
        if(2*d-n>=k) return true;
        return false;
    }
    int main()
    {
    //    freopen("1.in","r",stdin);
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d",&n,&k);
            for(int i=1;i<=n;++i) scanf("%d",&a[i]),sum[i]=0;
            for(int i=1;i<=n;++i) sum[a[i]]++;
            for(int i=1;i<=n;++i) sum[i]+=sum[i-1];
            pair<int,int>ans;
            ans={1,n};
            for(int x=1;x<=n;++x)
            {
                int l=x,r=n;
                while(l<r)
                {
                    int mid=l+r>>1;
                    if(check(x,mid)) r=mid;
                    else l=mid+1;
                }
                if(check(x,l)) 
                {
                    if(l-x<ans.second-ans.first)
                        ans={x,l};
                }
            }
            num=0;
            int x=ans.first,y=ans.second,l=1,sum=0;
            for(int i=1;i<=n;++i)    
            {
                if(a[i]>=x&&a[i]<=y) sum++;
                else sum--;
                if(sum>0)
                {
                    b[++num]={l,i};
                    sum=0;l=i+1;
                }
            }
            printf("%d %d\n",x,y);
            for(int i=1;i<k;++i) printf("%d %d\n",b[i].first,b[i].second);
            printf("%d %d\n",b[k-1].second+1,n);
        }
        return 0;   
    } 
    
    E. Paint the Middle

    这个题就是我跳过D题直接干的题,但有点小失误,作假了....
    首先我们可以发现相同的两个数之间的所有的数都可以染色(两个端点除外)。之后我们可以发现,其实我们只需要关注每个权值第一次出现的位置和最后一次出现的位即可。这样的话,我们就变成区间覆盖的问题,处理过每个权值构成的区间后,题目就变成了一个个区间相交,能最多染多少颜色的问题?我们发现非端点的区间内的点很好处理,他们一定能被染色,最麻烦的就是端点的问题,一个区间还好说,肯定两个端点都不能染,若两个区间相交,可以发现有三个端点不能染色,之后类推,可以发现n个区间相交的话,有n+1个端点不能染色。但注意这里相交的定义,我们的规定是必须是每个区间都为构成的大区间有贡献,若某个区间被删掉后,整个大区间不变,则这个区间应该被剔除。(我就是在这个点上跨了...)考虑怎么做,可以采用DP的形式,f[i]表示到区间右端点再i这个点的最大的答案是多少。我们将所有的区间按左端点从小到大排序。转移直接从1 到i-1,和i之后分类转移即可。发现这个过程可以用线段树优化,直接莽就行了。

    点击查看代码
    #include<bits/stdc++.h>
    #define ls p<<1
    #define rs p<<1|1
    using namespace std;
    const int N=2e5+10;
    int n,fi[N],la[N],a[N],f[N];
    vector<pair<int,int> >ve;
    struct Tree
    {
        int l,r,dat[3];
        #define l(p) t[p].l
        #define r(p) t[p].r
        #define dat(x,y) t[x].dat[y]
    }t[N*25];
    inline void build(int p,int l,int r)
    {
        l(p)=l;r(p)=r;
        dat(p,2)=-1e8;
        if(l==r) return;
        int mid=l+r>>1;
        build(ls,l,mid);
        build(rs,mid+1,r);
    }
    inline void alter(int p,int x,int v,int op)
    {
        if(l(p)==r(p)) {dat(p,op)=v;return;}
        int mid=l(p)+r(p)>>1;
        if(x<=mid)  alter(ls,x,v,op);
        else alter(rs,x,v,op);
        dat(p,op)=max(dat(ls,op),dat(rs,op));
    }
    inline int ask(int p,int l,int r,int op)
    {
        if(l<=l(p)&&r>=r(p)) return dat(p,op);
        int mid=l(p)+r(p)>>1,ans=-1e8;
        if(l<=mid) ans=max(ans,ask(ls,l,r,op));
        if(r>mid) ans=max(ans,ask(rs,l,r,op));
        return ans;
    }
    int main()
    {
    //    freopen("1.in","r",stdin);
        scanf("%d",&n);
        for(int i=1;i<=n;++i) scanf("%d",&a[i]);
        for(int i=1;i<=n;++i) 
        {
            if(!fi[a[i]]) fi[a[i]]=i;
            la[a[i]]=i;
        }
        for(int i=1;i<=n;++i) if(la[i]-fi[i]>1) ve.push_back({fi[i],la[i]});
        sort(ve.begin(),ve.end());
        build(1,1,n);
        for(auto s:ve)
        {
            int l=s.first,r=s.second;
            f[r]=max(f[r],ask(1,1,l,1)+r-l-1);
            f[r]=max(f[r],ask(1,l,r,2)+r-1);
            alter(1,r,f[r],1);alter(1,r,f[r]-r,2); 
        }
        printf("%d",dat(1,1));
        return 0;
    } 
    
  • 相关阅读:
    vi和vim区别
    基本命令
    Ubentu搭建nfs服务器
    Ubuntu + CentOS7 搭建tftp Server
    基于UP-CUP6410点灯实验完成
    搭建服务器(3)
    搭建服务器(2)
    CentOS7.1搭建服务器篇(1)
    关于科台斯k97gprs调试记录(1)
    Arm环境搭建-基于博创科技(CentOS7.0系统安装篇1)
  • 原文地址:https://www.cnblogs.com/gcfer/p/15852868.html
Copyright © 2020-2023  润新知