zoukankan      html  css  js  c++  java
  • USACO 2017 February Platinum

    第二次参加USACO 本来打算2016-2017全勤的 January的好像忘记打了 听群里有人讨论才想起来
    铂金组三题很有意思,都是两个排列的交叉对问题 我最后得分889/1000(真的菜)


    T1.Why Did the Cow Cross the Road
    题目大意:给出两个N个排列(N<=100,000),允许把其中一个排列循环移动任意位,a[i]表示i在第一个排列中的位置,b[i]表示第二个,定义交叉对(i,j)满足a[i]<a[j]且b[i]>b[j],求最少交叉对。
    思路:数字大小没有影响,于是令第一个排列中第一个出现的编号为1,第二个出现的编号为2,于是变成最小化第二个排列的逆序对,一开始的逆序对可以O(nlogn)求出,然后考虑把当前第一个数移到最后,若这个数编号为x,明显多了n-x个逆序对,少了x-1个逆序对,就可以O(n)求出所有循环移动第二个排列的情况,然后我开心的交了,开心的WA了,后来才知道,自己忘记考虑移第一个的情况了(其实只要反过来再做一遍)。
    得分:8/10(幸好数据大多是移第二个的?)

    #include<cstdio>
    #include<iostream>
    using namespace std;
    char B[1<<26],*S=B,C;int X;
    inline int read()
    {
        while((C=*S++)<'0'||C>'9');
        for(X=C-'0';(C=*S++)>='0'&&C<='9';)X=(X<<3)+(X<<1)+C-'0';
        return X;
    }
    #define MN 100000
    #define N 131072
    int f[MN+5],a[MN+5],t[N*2];
    void inc(int k){for(k+=N;k;k>>=1)++t[k];}
    int query(int l,int r)
    {
        int res=0;
        for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1)
        {
            if(~l&1)res+=t[l+1];
            if( r&1)res+=t[r-1];
        }
        return res;
    }
    int main()
    {
        freopen("mincross.in","r",stdin);
        freopen("mincross.out","w",stdout);
        fread(B,1,1<<26,stdin);
        int n=read(),i;long long ans,cnt=0;
        for(i=1;i<=n;++i)f[read()]=i;
        for(i=1;i<=n;++i)cnt+=query(a[i]=f[read()],n),inc(a[i]);
        for(ans=cnt,i=1;i<n;++i)if((cnt+=n-(a[i]<<1)+1)<ans)ans=cnt;
        cout<<ans;
    }
    View Code

    AC代码

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    using namespace std;
    char B[1<<26],*S=B,C;int X;
    inline int read()
    {
        while((C=*S++)<'0'||C>'9');
        for(X=C-'0';(C=*S++)>='0'&&C<='9';)X=(X<<3)+(X<<1)+C-'0';
        return X;
    }
    #define MN 100000
    #define N 131072
    int c[MN+5],d[MN+5],f[MN+5],a[MN+5],t[N*2];
    void inc(int k){for(k+=N;k;k>>=1)++t[k];}
    int query(int l,int r)
    {
        int res=0;
        for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1)
        {
            if(~l&1)res+=t[l+1];
            if( r&1)res+=t[r-1];
        }
        return res;
    }
    int main()
    {
        freopen("mincross.in","r",stdin);
        freopen("mincross.out","w",stdout);
        fread(B,1,1<<26,stdin);
        int n=read(),i;long long ans,cnt=0;
        for(i=1;i<=n;++i)f[c[i]=read()]=i;
        for(i=1;i<=n;++i)cnt+=query(a[i]=f[d[i]=read()],n),inc(a[i]);
        for(ans=cnt,i=1;i<n;++i)if((cnt+=n-(a[i]<<1)+1)<ans)ans=cnt;
        for(i=1;i<=n;++i)f[d[i]]=i;
        memset(t,cnt=0,sizeof(t));
        for(i=1;i<=n;++i)cnt+=query(a[i]=f[c[i]],n),inc(a[i]);
        for(ans=min(ans,cnt),i=1;i<n;++i)if((cnt+=n-(a[i]<<1)+1)<ans)ans=cnt;
        cout<<ans;
    }

    T2.Why Did the Cow Cross the Road II
    题目大意:两个排列排成两排(长度<=100,000),两排间相差不超过4的可以连边,边不能有交叉,问最多连几条。
    思路:金组也有这道,不过N只有1000,考虑DP,f[i][j]表示第一个排列用到第i个 ,第二个排列用到第j个,最多连几条边,f[i][j]=max(f[i-1][j],f[i][j-1]),若a[i]和b[j]相差不超过4,f[i][j]=max(f[i][j],f[i-1][j-1]+1),就能O(n^2)完成。由于对于每个a[i],相差不超过4的b[j]的最多9个,我们把状态的j改成用了j,每个f[i]相对f[i-1]只变了9个,然后拿线段树维护一下就可以O(9nlogn)来DP了。
    得分:10/10

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    char B[1<<26],*S=B,C;int X;
    inline int read()
    {
        while((C=*S++)<'0'||C>'9');
        for(X=C-'0';(C=*S++)>='0'&&C<='9';)X=(X<<3)+(X<<1)+C-'0';
        return X;
    }
    #define MN 100000
    #define N 131072
    int a[MN+5],b[MN+5],t[N*2],g[10];
    void renew(int k,int x){for(k+=N;k;k>>=1)t[k]=max(t[k],x);}
    int query(int l,int r)
    {
        int res=0;
        for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1)
        {
            if(~l&1)res=max(res,t[l+1]);
            if( r&1)res=max(res,t[r-1]); 
        }
        return res;
    }
    int main()
    {
        freopen("nocross.in","r",stdin);
        freopen("nocross.out","w",stdout);
        fread(B,1,1<<26,stdin);
        int n=read(),i,j;
        for(i=1;i<=n;++i)a[i]=read();
        for(i=1;i<=n;++i)b[read()]=i;
        for(i=1;i<=n;++i)
        {
            for(j=-4;j<=4;++j)if(a[i]+j>0&&a[i]+j<=n)g[j+4]=query(1,b[a[i]+j]-1)+1;
            for(j=-4;j<=4;++j)if(a[i]+j>0&&a[i]+j<=n)renew(b[a[i]+j],g[j+4]);
        }
        printf("%d",query(1,n));
    }

    T3.Why Did the Cow Cross the Road III
    题目大意:两个排列(长度<=100,000),相等的连边,相差超过K的称为不友好,求不友好交叉对。
    思路:我比较菜,只会暴力,考虑对每对相等的计算答案,每对a[i]=b[j],把a[i]左边设成1,右边设成0,b[j]同样,与这条边有交叉的边必然被编成一个1,1个0,也就是异或为1,考虑统计答案,做法1:把每个值现在的两个编码的异或值用树状数组维护,把每条边的当成一个二维询问,考虑莫队,插入和删除一个值都相当于令这个值编码的异或值异或上1,复杂度O(n^1.5logn);做法2:用可持久化bitset搞出两个排列所有前缀后缀的bitset,bitset里存每个值的编码,每条边都拿出bitset异或一下,复杂度O(n^2/32)。我比较懒,写了莫队,一开始T了六七个,调了下块大小,最后在K=3n^0.5左右只T两个点,玄妙无比。另外用sum(n)-sum(i+k)统计貌似比sum(i-k-1)多过了一个点,原理不明。
    得分:13/15

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    char B[1<<26],*S=B,C;int X;
    inline int read()
    {
        while((C=*S++)<'0'||C>'9');
        for(X=C-'0';(C=*S++)>='0'&&C<='9';)X=(X<<3)+(X<<1)+C-'0';
        return X;
    }
    #define MN 100000
    #define K 1050
    #define lb(x) (x&-x)
    int a[MN+5],b[MN+5],f[MN+5],s[MN+5];
    struct query{int a,b,x;}q[MN+5];
    bool cmp(query a,query b){return a.a/K==b.a/K?a.b<b.b:a.a<b.a;}
    int sum(int x){int r=0;for(;x;x-=lb(x))r+=s[x];return r;}
    void rev(int x){int r=(f[x]^1)-f[x];f[x]^=1;for(;x<=MN;x+=lb(x))s[x]+=r;}
    int main()
    {
        freopen("friendcross.in","r",stdin);
        freopen("friendcross.out","w",stdout);
        fread(B,1,1<<26,stdin);
        int n,k,i,pa=0,pb=0;long long ans=0;
        n=read();k=read();
        for(i=1;i<=n;++i)a[i]=read(),q[a[i]].a=i,q[a[i]].x=a[i]+k;
        for(i=1;i<=n;++i)b[i]=read(),q[b[i]].b=i;
        sort(q+1,q+n+1,cmp);
        for(i=1;i<=n;++i)
        {
            if(q[i].x>=n)continue;
            while(pa<q[i].a)rev(a[++pa]);
            while(pa>q[i].a)rev(a[pa--]);
            while(pb<q[i].b)rev(b[++pb]);
            while(pb>q[i].b)rev(b[pb--]);
            ans+=sum(n)-sum(q[i].x);
        }
        cout<<ans;
    }
    View Code

     正解:把每个数字在两个排列中的位置抽象到二维平面上(例如1在第一个排列中排第2个,在第二个排列中排第3,则用(2,3)表示),那么若两个数字交叉,则其中一个数字在二维平面上必然在另一个点的左上方(一维小,另一维大),要统计不友好交叉对,对每个点i统计1~i-k-1,i+k+1~n中有多少个点在他左上就好了,考虑把两个分开处理,可以依次插入点i,询问点i+k+1,插入点i+1,询问点i+k+2……另一个反过来再做一遍就能算出答案。这个问题可以用cdq分治或者二维线段树解决,复杂度O(nlogn^2)。

    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    using namespace std;
    #define ll long long
    char B[1<<26],*S=B,C;int X;
    inline int read()
    {
        while((C=*S++)<'0'||C>'9');
        for(X=C-'0';(C=*S++)>='0'&&C<='9';)X=(X<<3)+(X<<1)+C-'0';
        return X;
    }
    #define MN 100000
    int a[MN+5],b[MN+5],t[MN*2+5],x[MN*2+5],y[MN*2+5],s[MN+5];ll ans;
    struct query{int x,y,t;}q[MN*2+5];
    bool cmp(query a,query b){return a.x<b.x;}
    void inc(int x,int z){for(;x<=MN;x+=x&-x)s[x]+=z;}
    int sum(int x){int r=0;for(;x;x-=x&-x)r+=s[x];return r;}
    void solve(int l,int r)
    {
        if(l>=r)return;
        int i,mid=l+r>>1,qn;
        for(i=l,qn=0;i<=mid;++i)if(t[i])q[++qn]=(query){x[i],y[i],t[i]};
        for(i=mid;++i<=r;)if(!t[i])q[++qn]=(query){x[i],y[i],t[i]};
        sort(q+1,q+qn+1,cmp);
        for(i=1;i<=qn;++i)
            if(q[i].t)inc(q[i].y,1);
            else ans+=sum(MN)-sum(q[i].y);
        for(i=1;i<=qn;++i)if(q[i].t)inc(q[i].y,-1);
        solve(l,mid);solve(mid+1,r);
    }
    int main()
    {
        freopen("friendcross.in","r",stdin);
        freopen("friendcross.out","w",stdout);
        fread(B,1,1<<26,stdin);
        int n,k,i,cnt;
        n=read();k=read();
        for(i=1;i<=n;++i)a[read()]=i;
        for(i=1;i<=n;++i)b[read()]=i;
        for(cnt=0,i=1;i+k+1<=n;++i)
        {
            x[++cnt]=a[i];y[cnt]=b[i];t[cnt]=1;
            x[++cnt]=a[i+k+1];y[cnt]=b[i+k+1];t[cnt]=0;
        }
        solve(1,cnt);
        for(cnt=0,i=n;i-k-1;--i)
        {
            x[++cnt]=a[i];y[cnt]=b[i];t[cnt]=1;
            x[++cnt]=a[i-k-1];y[cnt]=b[i-k-1];t[cnt]=0;
        }
        solve(1,cnt);
        cout<<ans;
        fclose(stdin);fclose(stdout);return 0;
    }
  • 相关阅读:
    sqlalchemy 转json 的几种常用方式
    程序员的思维模型指南
    软件的本质
    Python数据模型及Pythonic编程
    Linux Kernel C语言编程范式
    U-Boot内存管理
    Linux网络文件系统的实现与调试
    Linux内核内存管理架构
    Linux多核并行编程关键技术
    Go/Python/Erlang编程语言对比分析及示例
  • 原文地址:https://www.cnblogs.com/ditoly/p/USACO-2017-2.html
Copyright © 2011-2022 走看看