AtCoder Beginner Contest 242 题解

C - 1111gal password

C 题可不可以算给 Beginner 打开了数数的大门啊。

定义状态 fi,j 表示长度为 i 的符合题目要求的数字,结尾数位是 j 的有多少个。那么递推式就是:

fi,j=fi1,j1+fi1,j+fi1,j+1fi,0=fi,10=0f1,j=1

然后就可以数数了。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mod=998244353;

int n;
ll f[1000005][11];

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin>>n;
    for(int i=1;i<=9;i++)f[1][i]=1;
    for(int j=2;j<=n;j++){
        for(int i=1;i<=9;i++)f[j][i]=(f[j-1][i-1]+f[j-1][i]+f[j-1][i+1])%mod;
    }
    cout<<accumulate(f[n]+1,f[n]+10,0ll)%mod;

    return 0;
}

D - ABC Transform

很有趣味!

首先可以看出一个字符执行了 t 次操作以后的长度是好计算的,所以我们先把问题变成一个字符执行 t 次操作之后的第 k 个是什么。

上面的东西可以递归处理,定义函数 f(c,t,k) 表示字符 c 执行了 t 次操作得到的字符串的第 k 个是什么,只是有一个问题:t 太大了。

观察到每次操作会把字符串长度加倍,所以执行很少操作后,就变成了对 f(c,t,1) 求值。然而操作三次以后第一个字符等价于没有改变,所以可以直接得到结果。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

int n,q;
char s[100005];

void deal(int c,ll t,ll k){
    if(t==0){
        cout<<char(c+'A')<<'\n';
        return;
    }
    if(1ll<<t-1>=k)deal((c+1)%3,t-1,k);
    else deal((c+2)%3,t-1,k-(1ll<<t-1));
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin>>s+1;
    n=strlen(s+1);
    cin>>q;
    while(q--){
        ll t,k;
        cin>>t>>k;
        ll lgk=1;
        while(1ll<<lgk<k)lgk++;
        if(t>lgk){
            t-=(t-lgk)/3*3;
        }
        for(int i=1;;i++){
            if(k>1ll<<t)k-=1ll<<t;
            else{
                deal(s[i]-'A',t,k);
                break;
            }
        }
    }

    return 0;
}

E - (∀x∀)

这个题目的标题是怎么回事啊。

我们可以枚举两个字符串相同的前缀有多长,然后在后一个位置给串 X 填更小的字符,再之后的位置可以随便填了。

特殊地,需要判断 X 的前半段全部和 S 相同时,后半段是否小于等于 S

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mod=998244353;
const int N=1e6;

int n,pw[N+5];
char s[N+5];

void mian(){
    cin>>n>>s+1;
    int ans=0;
    bool any=1;
    for(int i=1;i<=(n+1)/2;i++){
        int a=s[i]-'A',b=s[n+1-i]-'A';
        ans=(ans+(ll)a*pw[(n+1)/2-i])%mod;
        any&=a<=b;
        any|=a<b;
    }
    cout<<(ans+any)%mod<<'\n';
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    pw[0]=1;
    for(int i=1;i<=N;i++)pw[i]=pw[i-1]*26ll%mod;
    int T;
    cin>>T;
    while(T--)mian();

    return 0;
}

F - Black and White Rooks

题目标题让我想起了一个叫 black_white_tony 的人。

首先可以考虑把 RC 列分配给黑棋,PQ 列分配给白棋的方案数,显然是 (NR)(MC)(NRP)(MCQ)

然后我们考虑在 RC 列里填入 N 个棋子,允许有空着的行或者空着的列的方案数,是 (RCN)

当然,上面的枚举分配给黑白棋子的行列要求不能有空着的行列,那么我们可以尝试从 (RCN) 里去掉存在空着的行或者列的方案数。

记录没有空着的行列的方案数为 f(R,C,N)

f(R,C,N)=(RCN)r=1Rc=1C(Rr)(Cc)f(r,c,N)[rRcC]

思路是从允许空着的方案数里面减去强制一些行和列空着,然后给剩下的行列放入 N 个的方案数。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mod=998244353;
const int precLen=2500;

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 fact[precLen+5],inv[precLen+5];

ll binom(int n,int r){
    if(n<r||r<0)return 0;
    return (ll)fact[n]*inv[n-r]%mod*inv[r]%mod;
}

void initialization(){
    fact[0]=1;
    for(int i=1;i<=precLen;i++){
        fact[i]=(ll)fact[i-1]*i%mod;
    }
    inv[precLen]=qpow(fact[precLen],mod-2);
    for(int i=precLen-1;i>=0;i--){
        inv[i]=(ll)inv[i+1]*(i+1)%mod;
    }
}

ll mem[55][55][2505];

ll put(int r,int c,int n){
    if(r*c<n||max(r,c)>n)return 0;
    ll &res=mem[r][c][n];
    if(res)return res;
    res=binom(r*c,n);
    for(int i=1;i<=r;i++)for(int j=n/i;j<=c;j++)if(i!=r||j!=c){
        res=(res-binom(r,i)*binom(c,j)%mod*put(i,j,n)%mod+mod)%mod;
    }
    return res;
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    initialization();
    int n,m,b,w;
    cin>>n>>m>>b>>w;
    ll ans=0;
    for(int i=1;i<n;i++)for(int j=1;j<m;j++){
        ll coef1=binom(n,i)*binom(m,j)%mod*put(i,j,b)%mod;
        for(int k=1;i+k<=n;k++)for(int l=1;j+l<=m;l++){
            ll coef2=binom(n-i,k)%mod*binom(m-j,l)%mod*put(k,l,w)%mod;
            ans=(ans+coef1*coef2)%mod;
        }
    }
    cout<<ans<<'\n';

    return 0;
}

G - Range Pairing Query

莫队很厉害!

把询问 (l,r) 离线下来,按照 (l/N,r) 升序排序,然后从前往后枚举排序好的询问区间,并且维护实际计算入的区间的左右端点、每个值有出现多少次和答案。在考虑一个新的询问区间时,移动左右端点,就可以计算出答案。

由于右端点一直上升,只有 O(N) 次会从大变小,所以右端点移动次数是 O(NN) 的。

左端点一直在 N 的块内移动,只有 O(N) 次会从一个块走到另一个,所以移动次数也是 O(NN) 的。

移动左右端点,其实就是把当前考虑的值的集合加入或删除一个。维护每个值的出现次数,在增加到偶数时给答案加一,减少到奇数时给答案减一即可。

#include<bits/stdc++.h>
using namespace std;

const int B=316;
int n,q,a[100005],c[100005],res,ans[1000005];
pair<pair<int,int>,int> qs[1000005];

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];
    cin>>q;
    for(int i=1;i<=q;i++){
        cin>>qs[i].first.first>>qs[i].first.second;
        qs[i].second=i;
    }
    sort(qs+1,qs+1+q,[&](const pair<pair<int,int>,int>& a,const pair<pair<int,int>,int>& b){
        if(a.first.first/B!=b.first.first/B)return a.first.first/B<b.first.first/B;
        if(a.first.second!=b.first.second)return a.first.second<b.first.second;
        return a.second<b.second;
    });
    int l=1,r=0;
    for(int i=1;i<=q;i++){
        int ql,qr;
        tie(ql,qr)=qs[i].first;
        while(r<qr){
            ++r;
            res+=c[a[r]]&1;
            ++c[a[r]];
        }
        while(l>ql){
            --l;
            res+=c[a[l]]&1;
            ++c[a[l]];
        }
        while(r>qr){
            --c[a[r]];
            res-=c[a[r]]&1;
            --r;
        }
        while(l<ql){
            --c[a[l]];
            res-=c[a[l]]&1;
            ++l;
        }
        ans[qs[i].second]=res;
    }
    for(int i=1;i<=q;i++)cout<<ans[i]<<'\n';

    return 0;
}

Ex - Random Painting

赛场上不会 Min-Max 容斥也不会 EGF 推算的我真的好蠢啊。

给定一个随机元素的集合 S,每个元素都有一个随机取值 x。那么记最小值的期望值为 Min(S),最大值的期望值为 Max(S),有结论:

Max(S)=TS(1)|T|+1Min(T)

也能理解成一组集合中每个元素都有一个单位时间被选中的概率,Min(S) 为至少选中一个的期望时间,Max(S) 为全部选完的期望时间。

那么我们在本题中,可以求出每种位置集合,当中至少一个位置被选中的期望时间,就可以用 Min-Max 容斥变成所有位置都被选中的期望时间了。

我们可以 fi,j,k 表示确定了前 i 个位置选或者不选,第 i 个一定选,没有覆盖选了的位置的线段有 j 个,选的位置的个数的奇偶性是 k 的方案数。现在就可以枚举上一个选择的位置是什么,然后转移。最后使用虚拟状态 fn+1,i,0/1 计算答案。

#include<bits/stdc++.h>
#include<atcoder/modint>
using namespace std;

typedef long long ll;
typedef atcoder::modint998244353 mint;

mint qpow(mint a,ll n){
    mint res=1;
    while(n){
        if(n&1)res*=a;
        a*=a;
        n>>=1;
    }
    return res;
}

int n,m,sum[405][405];
mint f[405][405][2];

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int l,r;
        cin>>l>>r;
        sum[l][r]++;
    }
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)sum[i][j]+=sum[i][j-1];
    f[0][0][1]=1;
    for(int i=1;i<=n+1;i++){
        int s=0;
        for(int j=i-1;j>=0;j--){
            s+=sum[j+1][i-1];
            for(int k=s;k<=m;k++){
                for(int pa=0;pa<2;pa++){
                    f[i][k][pa]+=f[j][k-s][pa^1];
                }
            }
        }
    }
    mint ans;
    for(int i=0;i<m;i++){
        ans+=f[n+1][i][1]*m/(m-i);
        ans-=f[n+1][i][0]*m/(m-i);
    }
    cout<<ans.val()<<'\n';

    return 0;
}
posted @   BurnoutAg7  阅读(423)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示