Atcoder Beginner Contest 380 題解 (A-G)

Showball發表於2024-11-20

Atcoder Beginner Contest 380 題解 (A-G)

題目連結

A - 123233

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

void Showball(){
    string s;
    cin>>s;
    sort(s.begin(),s.end());
    if(s=="122333") cout<<"Yes\n";
    else cout<<"No\n"; 
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}

B - Hurdle Parsing

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

void Showball(){
   string s;
   cin>>s;
   int cnt=0;
   for(auto c:s){
    if(c=='|'){
        if(cnt) cout<<cnt<<" ";
        cnt=0;
    }else{
        cnt++;
    }
   } 
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}

C - Move Segment

模擬亂搞,下標,邊界有點煩。

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

void Showball(){
    int n,k;
    cin>>n>>k;
    string s;
    cin>>s;
    vector<int> a,b;
    string order="";
    for(int i=0,j=0;i<n;){
        while(j<n&&s[i]==s[j]) j++;
        order+=s[i];
        if(s[i]=='1') a.push_back(j-i);
        else b.push_back(j-i);
        i=j;
    } 
    int one=0,zero=0;
    a[k-2]+=a[k-1];
    for(auto c:order){
        if(c=='1'){
            if(one==k-1) {
                one++;
                continue;
            }
            for(int i=0;i<a[one];i++) cout<<1;
            one++;
        }else{
            for(int i=0;i<b[zero];i++) cout<<0;
            zero++;
        }
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}

D - Strange Mirroring 思維

經典性質,以 \(01\) 串舉例。\(0 \rightarrow 01 \rightarrow0110\rightarrow01101001\)

\(0\) \(1\) \(1\) \(0\) \(1\) \(0\) \(0\) \(1\)

\(0\) \(1\) \(2\) \(3\) \(4\) \(5\) \(6\) \(7\)

我們發現性質:如果當前位 \(i\)\(popcount\), 即 \(2\) 進位制下 \(1\) 的個數是奇數,那麼該位為 \(1\),否則為 \(0\)

不妨令原串為 \(s\), 大小寫反轉後的串為 \(t\)。那麼我們可以根據這個性質判斷當前位落在哪個串中,然後

取模求位置即可。

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

void Showball(){
    string s,t;
    cin>>s;
    int n=s.size();
    t=s;
    for(int i=0;i<n;i++){
        if(islower(t[i])) t[i]=char(t[i]-32);
        else t[i]=char(t[i]+32);
    }

    int q;
    cin>>q;
    while(q--){
        i64 x;
        cin>>x;
        x--;
        i64 p=x/n;
        int r=x%n;
        if(__builtin_popcountll(p)&1) cout<<t[r]<<" ";
        else cout<<s[r]<<" ";
    }

}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;
    while(t--){
      Showball();
    }

    return 0;
}

E - 1D Bucket Tool 並查集

可以用並查集來進行維護相同的顏色。只需要維護出這段顏色的左端點以及區間長度即可。

對於查詢 \(2\) ,直接查詢 \(cnt\) 陣列即可。考慮查詢 \(1\) 。我們透過並查集的 \(find\) 函式查到 \(x\) 所在區間的左端點。

然後原本區間顏色數量減去區間長度大小。更新顏色,並且維護新顏色的數量。注意還需要判斷左右相鄰區間顏色是否相同,然後維護顏色並查集即可。

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

struct DSU{
    vector<int> p, sz;
    DSU(int n) : p(n + 1), sz(n + 1, 1){ 
        iota(p.begin(), p.end(), 0); 

    }

    int find(int x){
        return p[x] == x ? x : p[x] = find(p[x]);
    }

    bool same(int x, int y) { 
        return find(x) == find(y); 
    }

    bool merge(int x, int y){
        x = find(x), y = find(y);
        if (x == y) return false;
        if (x > y) swap(x, y);
        sz[x] += sz[y];
        p[y] = x;
        return true;
    }

};

void Showball(){
    int n,q;
    cin>>n>>q;
    DSU dsu(n+1);
    vector<int> cnt(n+2,1),color(n+2);
    iota(color.begin(),color.end(),0);
    while(q--){
        int op;
        cin>>op;
        if(op==1){
            int x,c;
            cin>>x>>c;
            int l=dsu.find(x);
            int len=dsu.sz[l];
            cnt[color[l]]-=len;
            color[l]=c;
            cnt[color[l]]+=len;
            if(color[dsu.find(l-1)]==color[l]) dsu.merge(l-1,l);
            if(color[dsu.find(l+len)]==color[l]) dsu.merge(l,l+len);
        }else{
            int c;
            cin>>c;
            cout<<cnt[c]<<"\n";
        }
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}

F - Exchange Game 博弈+狀壓

我們發現資料範圍很小,考慮狀態壓縮。分別儲存先手和後手以及桌上的牌型。

然後記憶化搜尋即可。如果一個狀態的後繼狀態中存在一個必敗態,那麼這個狀態一定是必勝態。(因為玩家很聰明,一定會走到這個狀態),否則當前狀態為必敗態。

具體實現參考程式碼及註釋。

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

void Showball(){
    int n,m,l;
    cin>>n>>m>>l;
    int len=n+m+l;
    vector<int> a(len);
    for(int i=0;i<len;i++) cin>>a[i];
    vector<vector<int>> dp(4100,vector<int>(4100));
    
    function<int(int,int)> dfs=[&](int x,int y){
        if(dp[x][y]) return dp[x][y];
        int st=x+y;
        for(int i=0;i<len;i++){//列舉桌上牌型
            if(st>>i&1) continue;
            for(int j=0;j<len;j++){//列舉當前先手牌型
                if(!(x>>j&1)) continue;
                if(a[j]>a[i]){//可以摸桌上牌
                    int t=x-(1<<j)+(1<<i);
                    int tt=dfs(y,t);
                    if(tt==2) return dp[x][y]=1;
                }else{//不能摸桌上牌
                    int t=x-(1<<j);
                    int tt=dfs(y,t);
                    if(tt==2) return dp[x][y]=1;
                }
            }
        }
        return dp[x][y]=2;
    };

    int x=0,y=0;
    for(int i=0;i<n;i++) x|=1<<i;
    for(int i=n;i<n+m;i++) y|=1<<i;

    int ans=dfs(x,y);
    cout<<(ans==1?"Takahashi\n":"Aoki\n");
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}

G - Another Shuffle Window 思維+期望+逆序對

先來一道前置例題CF749E

簡要題意:給出一個 \(1∼n\) 的排列,從中等機率的選取一個連續段,設其長度為 \(l\) 。對連續段重新進行等機率的全排列,求排列後整個原序列的逆序對的期望個數。

思路:考慮每一對 \((i,j)\) 對答案的貢獻。先求出更改前的逆序對數量。再考慮更改後的。不妨設 \(i<j\)

那麼如果 \(a_i>a_j\), 那麼如果選擇的區間包含 \((i,j)\) 就會有 \(\frac{1}{2}\)負貢獻。因為從逆序對變成了順序對。

反之,產生了 \(\frac{1}{2}\)正貢獻。區間包含 \((i,j)\) 的機率為:\(\frac{2\times (n-j+1)\times i}{n\times (n+1)}\) 。因此貢獻為:\(\frac{(n-j+1)\times i}{n\times (n+1)}\)

我們只需要列舉區間右端點,然後用樹狀陣列統計大於和小於右端點的下標之和即可算出貢獻。

統計逆序對使用樹狀陣列即可。

程式碼

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

void Showball(){
    int n;
    cin>>n;
    vector<int> a(n+1);
    for(int i=1;i<=n;i++) cin>>a[i];

    vector<array<i64,2>> tr(n+1);  
    auto add=[&](int op,int x,int v){
        for(;x<=n;x+=x&-x) tr[x][op]+=v;
    };

    auto getsum=[&](int op,int x){
        i64 ret=0;
        for(;x;x-=x&-x) ret+=tr[x][op];
        return ret;
    };

    double q,ans=0;
    for(int i=1;i<=n;i++){
        q+=getsum(0,n)-getsum(0,a[i]);
        add(0,a[i],1);
        ans-=(n-i+1)*(getsum(1,n)-getsum(1,a[i]));
        ans+=(n-i+1)*(getsum(1,a[i]));
        add(1,a[i],i);
    }
    ans/=n;ans/=(n+1);

    cout<<fixed<<setprecision(9)<<q+ans<<"\n";
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}

透過上面這個題,我們就會知道長度為 \(n\) 的排列的期望逆序對數為 \(\frac{1}{2}\times C_n^2\)

那麼我們去分別考慮每一個長度為 \(k\) 的區間。不妨把序列分成三段 \([1,i-1],[i,i+k-1],[i+k,n]\)

我們發現洗牌中間序列,對三段之間互相形成的逆序對不會有影響。那麼我們可以透過算出總的逆序對數和中間段的逆序對數,作差來得到兩邊區間的逆序對數。除以 \(n-k+1\) 即可。再加上中間區間的 期望逆序對數量

前一部分,我們直接使用樹狀陣列類似滑動視窗的維護即可, 中間區間的期望逆序對數量 就可以用上一題的啟發了。即為 \(\frac{k\times (k-1)}{4}\)

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

const int mod = 998244353;

void Showball(){
    int n,k;
    cin>>n>>k;
    vector<int> a(n+1);
    for(int i=1;i<=n;i++) cin>>a[i];

    vector<i64> tr(n+1);

    auto add=[&](int x,int v){
        for(;x<=n;x+=x&-x) tr[x]+=v;
    };

    auto getsum=[&](int x){
        i64 ret=0;
        for(;x;x-=x&-x) ret+=tr[x];
        return ret;
    };

    auto inv=[&](i64 a){
        i64 b=mod-2,ret=1;
        while(b){
            if(b&1) ret=ret*a%mod;
            a=a*a%mod;
            b>>=1;
        }
        return ret;
    };

    i64 sum=0;
    for(int i=n;i;i--){
        sum+=getsum(a[i]);
        add(a[i],1);
    } 

    for(int i=0;i<=n;i++) tr[i]=0;

    i64 cur=0;
    for(int i=k;i;i--){
        cur+=getsum(a[i]);
        add(a[i],1);
    }
    
    i64 ans=sum-cur;
    for(int i=1;i+k<=n;i++){
        add(a[i],-1);
        cur-=getsum(a[i]);
        cur+=getsum(n)-getsum(a[i+k]);
        add(a[i+k],1);
        ans=(ans+sum-cur)%mod;
    }

    ans=(ans*inv(n-k+1)%mod+1LL*k*(k-1)%mod*inv(4)%mod)%mod;
    cout<<ans<<"\n";
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}

相關文章