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;
}