[题解]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\)时,答案为:

\[\sum\limits_{i=1}^n \min\big(\text{popcount}(B_i\oplus X),m-\text{popcount}(B_i\oplus X)\big) \]

然而如果我们按这样枚举\(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\))。

那么上式的答案可以重新表示为:

\[\sum_{i=0}^m g(X,i)\times \min(i,m-i) \]

相当于利用原式累加的元素数量多(\(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;
}
posted @ 2025-03-08 23:46  Sinktank  阅读(658)  评论(0)    收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2025 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.