[题解]AtCoder Beginner Contest 396(ABC396) A~G
A - Triple Four
按题意模拟即可。
时间复杂度\(O(n)\)。
点击查看代码
#include<bits/stdc++.h>
#define N 110
using namespace std;
int n,a[N];
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n-2;i++){
if(a[i]==a[i+1]&&a[i]==a[i+2]){
cout<<"Yes\n";
return 0;
}
}
cout<<"No\n";
return 0;
}
B - Card Pile
开一个栈,先压进去\(100\)张\(0\),再按题意操作即可,1为压栈、2为弹栈。
时间复杂度\(O(Q)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int q;
stack<int> st;
signed main(){
for(int i=1;i<=100;i++) st.push(0);
cin>>q;
while(q--){
int op,x;
cin>>op;
if(op==1){
cin>>x;
st.push(x);
}else{
cout<<st.top()<<"\n";
st.pop();
}
}
return 0;
}
C - Buy Balls
下面均规定黑球为\(a\)数组,白球为\(b\)数组。
根据题意,\(a\)中选择的球数必须\(\ge b\)中选择的球数。
根据贪心的思想,不难想到对\(a,b\)分别从大到小排序。
然后我们枚举\(a\)中选择的数量\(x\),则此时的答案为\((\sum\limits_{i=1}^x a_i)+f_x\)(\(f_i\)表示\(b\)中长度\(\le i\)的前缀和的最大值)。对于每个\(x\)更新答案即可。
时间复杂度\(O(n\log n+m\log m)\)。
注意开long long。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define N 200010
using namespace std;
int n,m,a[N],b[N],f[N],ans;
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++) cin>>b[i];
sort(a+1,a+1+n,[](int a,int b){return a>b;});
sort(b+1,b+1+m,[](int a,int b){return a>b;});
for(int i=1;i<=m;i++) f[i]=f[i-1]+b[i];
for(int i=1;i<=m;i++) f[i]=max(f[i-1],f[i]);
for(int i=1;i<=n;i++){
a[i]+=a[i-1];
ans=max(ans,f[min(i,m)]+a[i]);
}
cout<<ans<<"\n";
return 0;
}
D - Minimum XOR Path
注意到\(n\le 10\),所以暴搜就可以。
具体来说,对于当前遍历到的点,记录其从起点\(1\)开始的异或和,如果该点是\(n\)就更新答案。
由于起始点固定,所以时间复杂度上界是\(O((n-1)!)\)。取到上界时,该图是完全图,起始点之后点顺序是任意的。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define N 15
using namespace std;
int n,m,ans=LLONG_MAX;
struct edge{int to,w;};
vector<edge> G[N];
bitset<N> vis;
void add(int u,int v,int w){G[u].emplace_back(edge{v,w});}
void dfs(int u,int sum){
if(u==n){
ans=min(ans,sum);
return;
}
if(vis[u]) return;
vis[u]=1;
for(auto i:G[u]){
dfs(i.to,sum^i.w);
}
vis[u]=0;
}
signed main(){
cin>>n>>m;
for(int i=1,u,v,w;i<=m;i++){
cin>>u>>v>>w;
add(u,v,w),add(v,u,w);
}
dfs(1,0);
cout<<ans<<"\n";
return 0;
}
E - Min of Restricted Sum
首先注意到每个二进制位对答案的贡献是独立的,所以我们可以按位考虑,因而仅需解决\(Z_i,A_i\in \{0,1\}\)的情况即可,第\(i\)位的话贡献就乘上\(2^i\)。
接下来考虑如何解决这个简化的问题。
先考虑判定是否无解。我们可以将\(A_1,\dots,A_n\)看作图上节点\(1,\dots,n\)的权值,\((X,Y,Z)\)则相当于双向边。那么,有结论:
无解\(\iff\)存在一个环,使得其上的一条边权\(\ne\)环上其他边权的异或和。
证明:设环上的节点按顺序排列为\(u_1,\dots,u_m\)。
根据异或的自反性,有\(A_{u_1}\oplus A_{u_2}=(A_{u_2}\oplus A_{u_3})\oplus(A_{u_3}\oplus A_{u_4})\oplus\dots\oplus(A_{u_m}\oplus A_{u_1})\),也即\(w(u_1,u_2)=w(u_2,u_3)\oplus\dots\oplus w(u_m,u_1)\)。其他边同理。
根据证明,我们发现,一个环上的任意一条边的限制,都可以用环上除它之外的所有边表示,所以说我们从环上任意撤走一条边,限制效果不会改变。
也就是说,原图的生成树(森林),和原图起到的限制效果是等价的。那我们就对原图跑生成树。
如果存在非生成树边\((u,v,w)\),使得\(u,v\)在生成树上的路径的异或和\(\ne w\),则无解。这样,我们就完成了无解的判定。
其中计算\(u\)到\(v\)的异或和,我们可以维护\(d_i\)表示\(i\)到根节点的异或和,则\(d_u\oplus d_v\)即为所求。
接下来考虑合法的情况如何求答案。
单独考虑生成树森林中的每一棵树。对于树\(T\),我们对它的节点进行染色,保证任意边\((u,v,w)\),都有\(A_u\oplus A_v=w\)。由于每个点只能取\(0,1\),所以不难发现总共只有\(2\)种填色方法,且它们的\(1\)位置是互补的。所以如果其中一种填色方案所用的\(1\)个数\(\le \frac{size(T)}{2}\),就选它,否则选另一种。
这样我们就完成了。时间复杂度\(O(m+n\log V)\)。
代码实现稍有冗长,但是不太想重写了(懒
点击查看代码
#include<bits/stdc++.h>
#define N 200010
#define M 100010
using namespace std;
struct tedge{int u,v,w;};
struct edge{int to,w;};
vector<edge> G[N];//存储生成树
vector<tedge> es;//存储非生成树边
int n,m,fa[N],sum[N],siz[N],C,ans[N];
bitset<N> vis,vs;
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void add(int u,int v,int w){G[u].emplace_back(edge{v,w});}
void dfs(int u){//处理sum[i],即上面所说的d[i]
if(vis[u]) return;
vis[u]=1;
for(auto i:G[u]) sum[i.to]=sum[u]^i.w,dfs(i.to);
}
int dfs2(int u,bool x){//染色,统计1的个数
siz[u]=vis[u]=1;
int ans=x;
for(auto i:G[u]) if(!vis[i.to]) ans+=dfs2(i.to,x^((i.w>>C)&1)),siz[u]+=siz[i.to];
return ans;
}
void dfs3(int u,bool x){//统计答案
vs[u]=1;
ans[u]+=x<<C;
for(auto i:G[u]) if(!vs[i.to]) dfs3(i.to,x^((i.w>>C)&1));
}
bool solve(){
for(C=0;C<31;C++){
for(auto i:es){
int u=i.u,v=i.v,w=i.w;
if((((sum[u]^sum[v])>>C)&1)^((w>>C)&1)) return 0;
}
vis=vs=0;
for(int i=1;i<=n;i++) if(!vis[i]){
int tmp=dfs2(i,1);//将i染成1,会让多少节点变成1
if(tmp<siz[i]-tmp){
dfs3(i,1);
}else dfs3(i,0);
}
}
return 1;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1,u,v,w;i<=m;i++){
cin>>u>>v>>w;
int tu=find(u),tv=find(v);
if(tu==tv){
es.emplace_back(tedge{u,v,w});
}else{
fa[tu]=tv;
add(u,v,w),add(v,u,w);
}
}
for(int i=1;i<=n;i++) dfs(i);
if(!solve()) cout<<"-1\n";
else{
for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
}
return 0;
}
F - Rotated Inversions
发现逆序对数量改变,仅当某一位变成\(0\)。
所以对于\(k\)的一个取值,定义\((p_1,p_2,\dots,p_g)\),其中\(p_i\)表示第\(i\)个\(0\)的位置。
对于\(i\in[1,g]\),其对答案的贡献是\(\sum\limits_{i=1}^{p_i-1}[B_i\ne 0]-\sum\limits_{i=p_i+1}^{n}[B_i\ne 0]\),也即\((p_i-i)-[n-p_i-(g-i)]\)。
将每个\(p_i\)的贡献累加到上一个\(k\)的答案上,即为当前\(k\)的答案。
在枚举\(i\)之前,我们只需要计算出初始状态下的逆序对个数即可。
时间复杂度为\(O(n\log m+n)\)。
其中\(O(n\log m)\)是树状数组计算逆序对的时间复杂度,你也可以使用\(O(n\log n)\)的归并排序或者其他算法。
注意开long long。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define N 200010
using namespace std;
int n,m,a[N],sum[N],ans;
vector<int> op[N];
inline int lowbit(int x){return x&-x;}
void add(int x,int k){
while(x<N) sum[x]+=k,x+=lowbit(x);
}
int query(int x){
int ans=0;
while(x) ans+=sum[x],x-=lowbit(x);
return ans;
}
signed main(){
cin>>n>>m;
for(int i=1,a;i<=n;i++){
cin>>a;
ans+=query(m)-query(a+1),add(a+1,1);
op[m-a].emplace_back(i);
}
for(int i=1;i<=m;i++){
cout<<ans<<"\n";
int cnt=0;
for(int j:op[i]){
cnt++;
ans+=(j-cnt)-(n-j-(op[i].size()-cnt));
}
}
return 0;
}
G - Flip Row or Col
规定\(A\)数组为\(n\)行\(m\)列。
由于\(m\)很小,我们考虑将二进制串\(A_i\)转为十进制整数\(B_i\)来考虑。
那么我们对列进行的操作也可以看作一个整数\(X\),每个\(B_i\)完成列操作后变成\(B_i\oplus X\)。
当我们对列执行的操作固定为\(X\)时,答案为:
然而如果我们按这样枚举\(X,i\),时间复杂度将是\(O(2^m n)\),无法通过。
定义\(g(X,y)\)表示满足下列条件的整数\(i\)的个数:
- \(i\in [1,n]\)。
- \(X\)和\(B_i\)的二进制表示中,恰好有\(y\)位不同(即\(\text{popcount}(B_i\oplus X)=y\))。
那么上式的答案可以重新表示为:
相当于利用原式累加的元素数量多(\(n\)),值域小(\(m\))的特点,将同一值的元素放在一起统计,从而减小了时间开销。
考虑如何快速计算\(g\),我们可以使用DP解决。
具体地,定义\(f[X][z][y]\)表示满足下列条件的整数\(i\)的个数:
- \(i\in [1,n]\)。
- \(X\)和\(B_i\)的二进制表示中,恰好有\(y\)位不同(即\(\text{popcount}(B_i\oplus X)=y\))。
- \(X\)和\(B_i\)的二进制表示中,仅有后\(z\)位可能不同。
那么\(g(X,y)=f[X][m][y]\)。
有状态转移:
- \(f[X][z][y]=f[X][z-1][y]\)。
- 若\(y>0\),\(f[X][z][y]+=f[X\oplus 2^{z-1}][z-1][y-1]\)。
时间复杂度\(O(nm+m^2 2^m)\);空间复杂度\(O(m^2 2^m)\),可以将\(f\)第二维滚掉优化成\(O(m 2^m)\)。
不优化空间
#include<bits/stdc++.h>
#define W 18
using namespace std;
int n,m,f[1<<W][W+5][W+5],ans=INT_MAX;
bitset<W> tmp;
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>tmp,f[tmp.to_ullong()][0][0]++;
for(int i=1;i<=m;i++){
for(int j=0;j<=i;j++){
for(int k=0;k<(1<<m);k++){
f[k][i][j]=f[k][i-1][j];
if(j) f[k][i][j]+=f[k^(1<<(i-1))][i-1][j-1];
}
}
}
for(int i=0;i<(1<<m);i++){
int sum=0;
for(int j=0;j<=m;j++) sum+=f[i][m][j]*min(j,m-j);
ans=min(ans,sum);
}
cout<<ans;
return 0;
}
优化空间
#include<bits/stdc++.h>
#define W 18
using namespace std;
int n,m,f[1<<W][W+5],ans=INT_MAX;
bitset<W> tmp;
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>tmp,f[tmp.to_ullong()][0]++;
for(int i=1;i<=m;i++){
for(int j=i;j>=0;j--){
for(int k=0;k<(1<<m);k++){
f[k][j]=f[k][j];
if(j) f[k][j]+=f[k^(1<<(i-1))][j-1];
}
}
}
for(int i=0;i<(1<<m);i++){
int sum=0;
for(int j=0;j<=m;j++) sum+=f[i][j]*min(j,m-j);
ans=min(ans,sum);
}
cout<<ans;
return 0;
}
浙公网安备 33010602011771号