Atcoder Grand Contest 048 解题报告 (A-D)

Pro A atcoder<S

  • 题意:给定字符串 \(s\) ,每次只能交换 \(s\) 中的相邻两个字符,求最小的交换次数使得 \(s>\) 'atcoder' 。如果无解输出 -1 。多组数据。

  • 数据范围:\(T\leq 100,|s|\leq 10^3\)

  • 做法:

    ​ 首先判掉无解( \(s\) 中全为 \(a\) )和已经满足条件的情况。在 \(s\) 中找到第一个不是 \(a\) 的字符,设其为 \(x\) ,若 \(x>t\) ,那么直接将 \(x\) 移到 \(t\) 的位置;否则移到 \(a\) 的位置。 可以证明这样是最优的(分类讨论以下即可)。

  • 代码:

    #include <bits/stdc++.h>
    const int N=1e3+5;
    using namespace std;
    const char g[]={'a','t','c','o','d','e','r','\0'};
    char s[N];
    int  l;
    bool cant(){
        for(int i=0;i<l;++i)
            if(s[i]!='a') return false;
        return true;
    }
    bool done(){
        for(int i=0;i<min(l,7);++i)
            if(s[i]>g[i]) return true;
            else if(s[i]<g[i]) return false;
        return l>7;
    }
    void solve(){
        l=strlen(s);
        if(cant()) {puts("-1");return;}
        if(done()) {puts("0"); return;}
        for(int i=0;i<l;++i){
            if(s[i]=='a') continue;
            if(s[i]<='t') {printf("%d\n",i);break;}
            else {printf("%d\n",i-1);break;}
        }
    }
    int main(){
     
        int T;scanf("%d",&T);
        while(T--)
            scanf("%s",s),solve();
        return 0;
    }
    

Pro B Bracket Score

  • 题意:给定 \(n\) 个变量,每个变量可以是 ()[] 中的一种。变量 \(i\)() 中的一种时有 \(a_i\) 贡献,否则有 \(b_i\) 贡献。当 \(n\) 个变量组成的字符串为合法字符串时,求最大的 \(n\) 个变量的贡献和。

  • 数据范围:\(n\leq 10^5\)\(n\) 是偶数

  • 做法:

    ​ 假设最后有 \(k\) 个位置是 [] ,分别为 \(x_1,x_2,\cdots,x_k\)

    ​ 我们假设 \((x_1,x_2),\cdots,(x_{k-1},x_k)\) 配对且对于每一对配对关系恒有 \(x_{pre}<x_{suf}\)\(x_2-x_1-1\) 必然是偶数,即 \(x_1\)\(x_2\) 的奇偶性不同。同理,那么 \(x_1,x_2,\cdots,x_k\)\(k\) 个数中必然恰好有 \(\frac{k}{2}\) 个奇数, \(\frac{k}{2}\) 个偶数。 实际上,恰好半奇半偶也正是 \(x_1,x_2,\cdots,x_k\) 合法的充分条件。我们可以通过构造方案来证明:

    • 我们一定可以找到一个奇数 \(x_i\) 和一个偶数 \(x_j\) 满足两者之间再无 \(x\)
    • 我们可以将 \(x_i\)\(x_j\) 配对并消去,这时 \(x\) 序列仍然满足半奇半偶的性质。
    • 重复上述两步即可。

    ​ 问题转化为找到一种 \(x_1,x_2,\cdots,x_k\) 满足半奇半偶的使得贡献最大的方案。假设选择 () 视作选 \(0\)[] 视作选 \(1\) ,实际上,每个 \(x_1,x_2,\cdots,x_k\) 满足半奇半偶的方案都对应着一种选 \(\frac{n}{2}\)\(0\)\(\frac{n}{2}\)\(1\) 的方案——这是一种“双射”关系。为什么呢?

    • 假设在某一种选 \(0\) 的位置为半奇半偶的方案下我们选了 \(c_0\)\(0\)\(c_1\)\(1\)\(c_0+c_1=1\)
    • \(0\) 的位置是半奇半偶的同时 \(1\) 的位置也必然是半奇半偶。
    • 我们将偶数位全部取反。此时 \(0\) 的个数为 \(c_0-\frac{c_0}{2}+\frac{c_1}{2}=\frac{n}{2}\) 。正好。

    ​ 问题转化为找到一种选择 \(\frac{n}{2}\)\(0\)\(\frac{n}{2}\)\(1\) 使得贡献最大的方案。先把 \(b_i\) 全部选择了,再把 \(a_i-b_i\) 排序后选择前 \(\frac{n}{2}\) 大即可。当然,在这之前要记住在偶数位交换 \(a,b\)

  • 代码:

    const int N=1e5+5;
    using namespace std;
    long long n,ans;
    long long a[N],b[N];
    int main(){
    
        r(n);
        for(int i=1;i<=n;++i) r(a[i]);
        for(int i=1;i<=n;++i) r(b[i]);
        for(int i=2;i<=n;i+=2) swap(a[i],b[i]);
        for(int i=1;i<=n;++i) ans+=a[i],b[i]-=a[i];
        sort(b+1,b+n+1,greater<int>() );
        for(int i=1;i<=n/2;++i)
            ans+=b[i];
        w(ans);
        return 0;
    }
    

Pro C Penguin Skating

  • 题意:有 \(n\) 只企鹅, \(m\) 个广场,初始时每只企鹅在一个广场上,且任意时刻一个广场上只有 \(1\) 只企鹅。你可以让某只企鹅向左滑或者向右滑。若让企鹅 A 向左滑,当 A 的左边没有企鹅时,它会滑倒广场 \(1\) ;否则假设在 A 左边距离 A 最近的企鹅在广场 \(x\) ,那么他就会滑到 \(x+1\) 。向右滑同理。给定 \(n\) 个企鹅的初始位置以及目标位置,问从初始位置变成目标位置的最少次数。无解输出 -1

  • 数据范围:\(n\leq 10^5,m\leq 10^9\)

  • 做法:

    ​ 设 \(\{a\}\) 表示企鹅的初始位置, \(\{b\}\) 表示目标位置。设 \(a_0=0,a_{n+1}=m+1,d_i=a_i-a_{i-1}-1\) ,也就是记录企鹅两两之间的距离。再设 \(b_0=0,b_{n+1}=m+1,e_i=b_i-b_{i-1}-1\) 。当 \(\forall i\in [1,n+1] \text{ s.t. } d_i=e_i\) 时,由于初始/结束位置均确定,且两两之间差值也确定,那么实际上原序列也就确定相同了。每次滑动企鹅,实际上就是 \(d_{i+1 \text{ or }i-1}-=d_i,d_i=0\) 的变换。贪心凑出来每一项 \(e_i\) 即可。

  • 代码:

    const int N=1e5+5;
    using namespace std;
    int n,L;
    long long a[N],b[N];
    int main(){
    
        r(n),r(L);
        for(int i=1;i<=n;++i) r(a[i]);
        for(int i=1;i<=n;++i) r(b[i]);
        a[n+1]=L+1,b[n+1]=L+1;
        for(int i=n+1;i>=1;--i)
            a[i]=a[i]-a[i-1]-1,b[i]=b[i]-b[i-1]-1;
        long long ans=0,j=1,st=1;
        for(int i=1;i<=n+1;++i){
            if(!b[i]) continue;
            while(!a[j]) ++j;st=j;
            long long cur=0; 
            while(cur<b[i]&&j<=n+1) cur+=a[j++];
            if(cur!=b[i]) {puts("-1");return 0;}
            ans+=max(0ll,j-1-i)+max(0ll,i-st);
        }
        w(ans);
        return 0;
    }
    

Pro D Pocky Game

  • 题意:有 \(n\) 堆石子,两个玩家轮流丢石子。1 号玩家每次只能从最左边的石子堆丢,2 号玩家每次只能从最右边的石子堆丢。每次可以在当前堆中丢弃任意个石子。无法操作的人输。给定初始的石子序列 \(\{a\}\) ,判定谁能赢。

  • 数据范围:\(n\leq 10^3,a_i\leq 10^9\)

  • 做法:

    ​ 首先有一个性质要把握:

    • 一个人每次要么拿一个,要么拿一堆。因为拿一个后的决策范围包含了拿 \(k(k\in(1,a_i))\) 个的决策范围。

    ​ 有了这个性质后,设 one[L][R] 表示目前只有 \([L,R]\) 这些石子堆,\(1\) 号先手时,\(a_L\) 至少为多少才能让\(1\) 号必胜。同理设 two[L][R]

    • \(a_R<two[L+1][R]\) 时。无论第 \(L\) 堆石子有多少个, 1 号直接取完就赢了。所以 \(one[L][R]=1\)
    • \(a_R\ge two[L+1][R]\) 时。双方肯定都会希望自己先把对方耗尽,所以均会一个一个取。假设第 \(L\) 堆有 \(x\) 个石子。当 1 号取到第 \(L\) 堆只剩 \(one[L][R-1]-1\) 个时,2 号必然一把将第 \(R\) 堆全取完;当 2 号取到第 \(R\) 堆只剩 \(two[L+1][R]-1\) 个时,1 号必然一把将第 \(L\) 堆全取完。 所以可得

    \[x-one[L][R-1]+1>a_R-two[L+1][R]+1\\ x>a_R-two[L+1][R]+one[L][R-1] \]

    ​ 所以 \(x\) 的最小值,也就是 \(one[L][R]=a_R-two[L+1][R]+one[L][R-1]\) 。求 two[][] 的过程类似。我们可以在 \(\mathcal{O}(n^2)\) 的时间内解决此题。

  • 代码:

    const int N=1e3+5;
    using namespace std;
    int n;
    long long a[N],L[N][N],R[N][N];
    void solve(){
        r(n);
        for(int i=1;i<=n;++i)
            r(a[i]),L[i][i]=R[i][i]=1;
        for(int k=2;k<=n;++k)
        for(int i=1;i+k-1<=n;++i){
            int j=i+k-1;
            if(a[j]<R[i+1][j]) L[i][j]=1;
            else L[i][j]=a[j]-R[i+1][j]+L[i][j-1]+1;
            if(a[i]<L[i][j-1]) R[i][j]=1;
            else R[i][j]=a[i]-L[i][j-1]+R[i+1][j]+1;
        }
        puts(a[1]>=L[1][n]?"First":"Second");
    }
    int main(){
    
        int T;r(T);
        while(T--)
            solve();
        return 0;
    }
    
posted @ 2020-10-20 09:14  ovor  阅读(215)  评论(0编辑  收藏  举报