• KYOCERA Programming Contest 2021(AtCoder Beginner Contest 200) 题解


    KYOCERA Programming Contest 2021(AtCoder Beginner Contest 200) 题解

    哦淦我已经菜到被ABC吊打了。

    A - Century

    首先把当前年份减去(1),对应的世纪也减去(1),然后我们就发现第(0)(99)年对应第(0)世纪,第(100)(199)年对应第(1)世纪,以此类推。

    答案就是(lfloor frac {N-1} {100} floor)。这里(lfloor x floor)表示不超过(x)的最大整数。

    #include<bits/stdc++.h>
    using namespace std;
    
    int main(){
    
        ios::sync_with_stdio(false);
        cin.tie(0);
        cout.tie(0);
    
        int n;
        cin>>n;
        cout<<(n-1)/100+1<<endl;
    
        return 0;
    }
    

    B - 200th ABC-200

    乍一看答案会很大,然后爆long long

    其实假如当前数不是(200)的倍数的时候,把它后面连接上(200),结果一定是(200)的倍数,因为(1000)一定是(200)的倍数。

    所以答案其实不会很大,最多就是(976555175781)(N=99999, K=20))。

    然后暴力做就好了。

    #include<bits/stdc++.h>
    using namespace std;
    
    int main(){
    
        ios::sync_with_stdio(false);
        cin.tie(0);
        cout.tie(0);
    
        long long n;
        int k;
        cin>>n>>k;
        while(k){
            if(n%200==0)n/=200,k--;
            else n=n*1000+200,k--;
        }
        cout<<n;
    
        return 0;
    }
    

    C - Ringo's Favorite Numbers 2

    假如两个数相减是(200)的倍数,他们对(200)取模后,得到的结果一定相同。

    然后就用前缀和记录在当前的位置之前有多少个数对(200)取模得到的结果和这个数对(200)取模的结果相同,并且把这个加入答案中就可以了。

    只有我一开始以为差一定要是非负数然后被坑了么。

    #include<bits/stdc++.h>
    using namespace std;
    
    int n,a[200005],c[205];
    long long ans;
    
    int main(){
    
        ios::sync_with_stdio(false);
        cin.tie(0);
        cout.tie(0);
    
        cin>>n;
        for(int i=1;i<=n;i++){
            cin>>a[i];
            ans+=c[a[i]%200];
            c[a[i]%200]++;
        }
        cout<<ans;
    
        return 0;
    }
    

    D - Happy Birthday! 2

    首先还是把所有的数都对(200)取模。

    然后我们可以想出来一种DP的状态表示方法:(f_{i,j})表示已经处理了前(i)个数,选出来的(B)序列和(C)序列的差对(200)取模等于(j)

    这种状态的转移,可以枚举当前的数是放进(B)中还是(C)中,亦或是两个序列都不放。输出方案也只需要记录上一个状态的(j),然后反着操作一遍就好了。

    但是它有一个问题,就是不能保证两个序列都是非空的。

    我们可以再开一维,记录两个序列当中是不是有元素了:(f_{i,j,k})表示已经处理了前(i)个数,选出来的(B)序列和(C)序列的差对(200)取模等于(j)(k)以子集的位掩码的形式记录两个序列是否非空。

    子集的位掩码这个表述非常有点奇怪,具体来说就是:

    • (k=0)表示两个序列都是空的。
    • (k=1)表示一个序列都是空的。
    • (k=2)表示另一个序列都是空的。
    • (k=3)表示两个序列都是非空的。

    (“一个”和“另一个”的表述是因为这不重要,我就懒得想了)

    然后为了输出方案还要记录上一个状态的(k)是多少。

    #include<bits/stdc++.h>
    using namespace std;
    
    int n,a[205],g[205][205][4],h[205][205][4];
    bool f[205][205][4];
    
    int main(){
    
        ios::sync_with_stdio(false);
        cin.tie(0);
        cout.tie(0);
    
        cin>>n;
        for(int i=1;i<=n;i++){
            cin>>a[i];
            a[i]%=200;
        }
        f[0][0][0]=1;
        for(int i=1;i<=n;i++){
            f[i][0][0]=1;
            for(int j=0;j<200;j++){
                for(int k=1;k<=3;k++){
                    if(f[i-1][(j+a[i])%200][k&1]){
                        f[i][j][k]=1;
                        g[i][j][k]=-1;
                        h[i][j][k]=k&1;
                    }
                    if(f[i-1][(j+a[i])%200][k]){
                        f[i][j][k]=1;
                        g[i][j][k]=-1;
                        h[i][j][k]=k;
                    }
                    if(f[i-1][j][k]){
                        f[i][j][k]=1;
                    }
                    if(f[i-1][(j-a[i]+200)%200][k&2]){
                        f[i][j][k]=1;
                        g[i][j][k]=1;
                        h[i][j][k]=k&2;
                    }
                    if(f[i-1][(j-a[i]+200)%200][k]){
                        f[i][j][k]=1;
                        g[i][j][k]=1;
                        h[i][j][k]=k;
                    }
                }
            }
        }
        if(!f[n][0][3]){
            cout<<"NO";
            return 0;
        }
        int x=n,y=0,z=3;
        cout<<"YES
    ";
        vector<int> u,v;
        while(x){
            if(g[x][y][z]==1){
                u.emplace_back(x);
                z=h[x][y][z];
                y=(y-a[x]+200)%200;
            }else if(g[x][y][z]==-1){
                v.emplace_back(x);
                z=h[x][y][z];
                y=(y+a[x])%200;
            }
            x--;
        }
        reverse(u.begin(),u.end());
        cout<<u.size()<<' ';
        for(int i:u)cout<<i<<' ';
        cout<<'
    ';
        reverse(v.begin(),v.end());
        cout<<v.size()<<' ';
        for(int i:v)cout<<i<<' ';
    
        return 0;
    }
    

    E - Patisserie ABC 2

    这里我们可以想到先枚举三种属性的和是多少。然后我们可以定义(tot(m)),表示和为(m)的时候,一共有多少种组合方式。

    然后由于每个属性的值都是(1)(n)的限制,又到了激动人心的分类讨论环节。

    首先我们把第一属性的值叫做(i),那么后两个属性的和就是(m-i)

    • (2leq m-ileq n+1):那么第二属性的取值范围就是(1)(m-i-1),答案就是(sum_{max(m-n-1,1)}^{min(n,m-2)}m-i-1)
    • (n+2leq m-ileq 2n):那么第二属性的取值范围就是(m-i-n)(n),答案就是(sum_{max(1,m-2*n)}^{min(n,m-n-2)}2n-(m-i)+1)

    那么:

    [tot(i)=sum_{max(m-n-1,1)}^{min(n,m-2)}(m-i-1)+sum_{max(1,m-2*n)}^{min(n,m-n-2)}(2n-(m-i)+1) ]

    然后就可以枚举(m),快速的削减(k)的值,直到(k)小于等于(tot(m))了,这时候我们就枚举第一属性(i),然后根据上文的分类讨论计算第二属性和第三属性的取值即可(同样是先快速削减(k),然后(k)小于等于当前第一属性对应取值种类数时输出答案)。

    #include<bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    
    int n;
    ll k;
    
    ll sum(int l,int r,int lv,int rv){
        return (ll)(lv+rv)*(r-l+1)/2;
    }
    
    ll tot(int m){
        ll res=0;
        if(m<=2*n+1){
            int l=max(1,m-n-1),r=min(n,m-2);
            res+=sum(l,r,m-l-1,m-r-1);
        }
        if(m>n+2){
            int l=max(1,m-2*n),r=min(n,m-n-2);
            res+=sum(l,r,2*n-(m-l)+1,2*n-(m-r)+1);
        }
        return res;
    }
    
    int main(){
    
        ios::sync_with_stdio(false);
        cin.tie(0);
        cout.tie(0);
    
        cin>>n>>k;
        for(int m=3;m<=3*n;m++){
            if(k>tot(m)){
                k-=tot(m);
                continue;
            }
            for(int i=1;i<=n;i++)if(m-i<=n*2){
                if(m-i<=n+1){
                    if(k>m-i-1){
                        k-=m-i-1;
                        continue;
                    }
                    cout<<i<<' '<<k<<' '<<m-i-k;
                    return 0;
                }else{
                    if(k>2*n-(m-i)+1){
                        k-=2*n-(m-i)+1;
                        continue;
                    }
                    cout<<i<<' '<<m-i-n-1+k<<' '<<n+1-k;
                    return 0;
                }
            }
        }
    
        return 0;
    }
    

    F - Minflip Summation

    由于我是看了题解才把这题做出来的,所以就照着题解翻译吧(还好意思讲)。

    假如(S)不包含?同时(K=1)的解法

    我们把所有左右相邻的,一个是(0)一个是(1)的位置(也就是(S_i eq S_{i+1})的位置(i))都数出来(比如10100能数出来(3)个)。

    为了叙述方便,我们把它叫做“切换位置”(毕竟是从(0)切换到(1)或者从(1)切换到(0))。

    然后发现一次操作,最多把它减少(2)(选择两个切换位置去操作),也可以减少(1)(选择一个切换位置,和一个开头/结尾)。

    那么假如数出来(D)个位置,就需要(lfloor frac D 2 floor)次操作解决。

    这道题的解法

    然后对于所有的这样的位置,我们记录他们的总和是(A):(主要是从AtCoder的题解上搬的,但是修了一点小锅)

    [A = 2^{Kq} imes ( ( K imes displaystyle sum_{i=1}^{|S|-1} f(S_i,S_{i+1}) ) + (K-1) imes f(S_1,S_{|S|})) ]

    [egin{eqnarray} f(a,b) = egin{cases} frac{1}{2} & ext{(if at least one of }a ext{ and }b ext{ is ?)} \ 1 & ext{(if neither }a ext{ nor }b ext{ is ? and }a eq b ext{)} \ 0 & ext{(if neither }a ext{ nor }b ext{ is ? and }a = b ext{)} \ end{cases} end{eqnarray} ]

    然后答案似乎应该是(frac A 2),不过还有一点问题:假如说一个串数出来奇数个切换位置,这种算法对于这个串会不会少算了半次操作?

    我们可以发现,对于有奇数个切换位置的串,多添加一个切换位置后答案相同。

    然后假如给(A)添加上这些串的个数,答案就是(frac A 2)了。

    这些串有一个性质:他们的开头和结尾,是不同的(毕竟切换了奇数次)。

    然后这些串的个数就是(2^{Kq} imes f(S_1,S_{|S|}))

    然后答案就是(frac {A+2^{Kq} imes f(S_1,S_{|S|})} 2),也就是:

    [2^{Kq} imes K imes sum_{i=1}^{|S|} f(S_i,S_{i+1}) ]

    (特别地,这里把(S_{|S|+1})看作是(S_1)

    #include<bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    const int mod=1e9+7;
    const int inv2=5e8+4;
    
    ll qpow(ll a,ll n){
        ll res=1;
        while(n){
            if(n&1)res=res*a%mod;
            a=a*a%mod;
            n>>=1;
        }
        return res;
    }
    
    int n,m,k,ans;
    char s[100005];
    
    int main(){
    
        ios::sync_with_stdio(false);
        cin.tie(0);
        cout.tie(0);
    
        cin>>s>>k;
        n=strlen(s);
        if(n*k==1){cout<<0;return 0;}
        for(int i=0;i<n;i++)m+=s[i]=='?';
        m=qpow(2,(ll)k*m)*inv2%mod*k%mod;
        for(int i=0;i<n;i++){
            if(s[i]=='?'||s[(i+1)%n]=='?')ans=(ans+m)%mod;
            else if(s[i]!=s[(i+1)%n])ans=(ans+m*2%mod)%mod;
        }
        cout<<(ll)ans*inv2%mod;
    
        return 0;
    }
    
  • 相关阅读:
    dynamic_debug和pr_debug()打印_高通平台
    Linux内核中的数据结构杂记
    Binder杂记
    内核中读写文件
    记一次阿里云ECS中Docker容器内无法连接RDS内网地址的故障解决
    mac上使用sips命令快速裁剪、旋转、翻转图片
    最新Android手机导出ANR、tombstones文件
    Android日期格式控制—— DateUtils
    addr2line的用法
    Spring-Cloud 学习笔记-(6)Feign
  • 原文地址:https://www.cnblogs.com/BlahDuckling747/p/KYOCERA-Programming-Contest-2021-AtCoder-Beginner-Contest-200-Solution.html
Copyright © 2020-2023  润新知