AtCoder Regular Contest 198 (Div. 2)

A - I hate 1

首先特判 \(n=1\) 这个 corner case。

然后有结论:选取 \(1\sim n\) 中所有偶数合法且最优。

看着就很对,考虑证明。

首先这个构造是合法的,因为所有数都是偶数所以余数不可能是奇数。

其次这个构造是最优的。因为如果选出相邻两个数就寄了,同时注意到 \(1\) 不能和别的数一起选,所以上界就是 \(\lfloor\frac{n}{2}\rfloor\)

#include<bits/stdc++.h>
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define per(i,r,l) for(int i=(r);i>=(l);i--)
#define repll(i,l,r) for(ll i=(l);i<=(r);i++)
#define perll(i,r,l) for(ll i=(r);i>=(l);i--)
#define pb push_back
#define ins insert
#define clr clear
using namespace std;
namespace ax_by_c{
typedef long long ll;
void slv(){
	int n;
	scanf("%d",&n);
	if(n==1){
		puts("1");
		puts("1");
		return ;
	}
	printf("%d\n",n/2);
	for(int i=2;i<=n;i+=2)printf("%d ",i);
}
void main(){
	int T=1;
	// int csid=0;scanf("%d",&csid);
    // scanf("%d",&T);
    while(T--)slv();
}
}
int main(){
	string __name="";
	if(__name!=""){
		freopen((__name+".in").c_str(),"r",stdin);
		freopen((__name+".out").c_str(),"w",stdout);
	}
	ax_by_c::main();
	return 0;
}

B - Rivalry

相当于 \(1\) 两边有恰好 \(1\)\(0\)\(2\) 两两不相邻。

相当于两个 \(0\) 之间可以插 \(0\sim 2\)\(1\)\(0\sim 1\)\(2\),并且 \(1\)\(1\) 时必须插 \(1\)\(2\)

首先如果 \(\lceil\frac{y}{2}\rceil>x\) 就无解了,因为 \(1\) 放不完。

如果 \(y\) 是偶数那么肯定是两两放一起,因为单独放还需要配 \(2\),因此 \(z\in[0,x]\) 就有解,否则无解。

如果 \(y\) 是奇数同理可知有一个 \(1\) 要配 \(2\),因此 \(z\in[1,x]\) 就有解,否则无解。

实际上可以写成 \(\lceil\frac{y}{2}\rceil\in[0,x]\)\(z\in[y\bmod 2,x]\)

#include<bits/stdc++.h>
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define per(i,r,l) for(int i=(r);i>=(l);i--)
#define repll(i,l,r) for(ll i=(l);i<=(r);i++)
#define perll(i,r,l) for(ll i=(r);i>=(l);i--)
#define pb push_back
#define ins insert
#define clr clear
using namespace std;
namespace ax_by_c{
typedef long long ll;
void slv(){
	int x,y,z;
	scanf("%d %d %d",&x,&y,&z);
	if((y+1)/2<=x&&y%2<=z&&z<=x)puts("Yes");
	else puts("No");
}
void main(){
	int T=1;
	// int csid=0;scanf("%d",&csid);
    scanf("%d",&T);
    while(T--)slv();
}
}
int main(){
	string __name="";
	if(__name!=""){
		freopen((__name+".in").c_str(),"r",stdin);
		freopen((__name+".out").c_str(),"w",stdout);
	}
	ax_by_c::main();
	return 0;
}

C - Error Swap

首先特判 \(n=2\) 这个 corner case。

序列和显然不变,所以 \(\sum a\neq\sum b\) 就无解。

这种题肯定先找基础操作,可以发现:

  • \((x,x+1),(x+1,y),(x,x+1),(x,y)\) 可以使 \(a_x\leftarrow a_x+1,a_y\leftarrow a_y-1\)

  • \((x,y),(x,x+1),(x+1,y),(x,x+1)\) 可以使 \(a_x\leftarrow a_x-1,a_y\leftarrow a_y+1\)

于是可以用 \(4\) 次操作使两个不相邻的元素转移 \(1\) 的量。

考虑从前往后扫,若当前位置需要增加就去后面找一个要减少的位置,反之同理,此处需要 \(2nV\) 次操作。

如果全部能找到就只需考虑 \(n-2,n-1,n\) 了,但是可能找不到。设当前位置为 \(i\),则 \(i+2\sim n\) 所需的变化和 \(i\) 是同类的,只有 \(i+1\) 不是。将 \(i+3\sim n\)\(i+1\) 操作后就只需考虑 \(i,i+1,i+2\) 了。

那么只需要考虑三个数的情况,容易调整第 \(1\)\(3\) 个数使得 \((1,2)\) 后第 \(2\) 个数复原,再次调整第 \(1\)\(3\) 个数即可。此处需要 \(8V+1\) 次操作。

总操作数 \(2nV+8V+1\) 可以通过,以下实现是 \(O(n^2V)\) 的。

#include<bits/stdc++.h>
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define per(i,r,l) for(int i=(r);i>=(l);i--)
#define repll(i,l,r) for(ll i=(l);i<=(r);i++)
#define perll(i,r,l) for(ll i=(r);i>=(l);i--)
#define pb push_back
#define ins insert
#define clr clear
using namespace std;
namespace ax_by_c{
typedef long long ll;
typedef pair<int,int> pii;
const int N=105;
int n,a[N],b[N];
vector<pii>ans;
void ad1(int x,int y){
	ans.pb({x,x+1});
	ans.pb({x+1,y});
	ans.pb({x,x+1});
	ans.pb({x,y});
}
void rm1(int x,int y){
	ans.pb({x,y});
	ans.pb({x,x+1});
	ans.pb({x+1,y});
	ans.pb({x,x+1});
}
void FF(int pos){
	int dlt=b[pos]+b[pos+2]-a[pos]-a[pos+2];
	int to=a[pos+1]-1-dlt;
	while(a[pos]<to){
		ad1(pos,pos+2);
		a[pos]++,a[pos+2]--;
	}
	while(a[pos]>to){
		rm1(pos,pos+2);
		a[pos]--,a[pos+2]++;
	}
	ans.pb({pos,pos+1});
	int L=a[pos],R=a[pos+1];
	a[pos]=R-1,a[pos+1]=L+1;
	while(a[pos]<b[pos]){
		ad1(pos,pos+2);
		a[pos]++,a[pos+2]--;
	}
	while(a[pos]>b[pos]){
		rm1(pos,pos+2);
		a[pos]--,a[pos+2]++;
	}
}
void slv(){
	scanf("%d",&n);
	rep(i,1,n)scanf("%d",&a[i]);
	rep(i,1,n)scanf("%d",&b[i]);
	if(n==2){
		if(a[1]==b[1]&&a[2]==b[2]){
			puts("Yes");
			puts("0");
			return ;
		}
		if(a[2]-1==b[1]&&a[1]+1==b[2]){
			puts("Yes");
			puts("1");
			puts("1 2");
			return ;
		}
		puts("No");
		return ;
	}
	int sa=0,sb=0;
	rep(i,1,n)sa+=a[i],sb+=b[i];
	if(sa!=sb){
		puts("No");
		return ;
	}
	puts("Yes");
	rep(i,1,n-2){
		while(a[i]<b[i]){
			int p=0;
			rep(j,i+2,n)if(a[j]>b[j])p=j;
			if(p){
				ad1(i,p);
				a[i]++,a[p]--;
			}
			else{
				per(x,n,i+3){
					while(a[x]<b[x]){
						rm1(i+1,x);
						a[i+1]--,a[x]++;
					}
				}
				FF(i);
				printf("%d\n",(int)ans.size());
				for(auto it:ans)printf("%d %d\n",it.first,it.second);
				return ;
			}
		}
		while(a[i]>b[i]){
			int p=0;
			rep(j,i+2,n)if(a[j]<b[j])p=j;
			if(p){
				rm1(i,p);
				a[i]--,a[p]++;
			}
			else{
				per(x,n,i+3){
					while(a[x]>b[x]){
						ad1(i+1,x);
						a[i+1]++,a[x]--;
					}
				}
				FF(i);
				printf("%d\n",(int)ans.size());
				for(auto it:ans)printf("%d %d\n",it.first,it.second);
				return ;
			}
		}
	}
	FF(n-2);
	printf("%d\n",(int)ans.size());
	for(auto it:ans)printf("%d %d\n",it.first,it.second);
}
void main(){
	int T=1;
//	int csid=0;scanf("%d",&csid);
//  scanf("%d",&T);
    while(T--)slv();
}
}
int main(){
	string __name="";
	if(__name!=""){
		freopen((__name+".in").c_str(),"r",stdin);
		freopen((__name+".out").c_str(),"w",stdout);
	}
	ax_by_c::main();
	return 0;
}

D - Many Palindromes on Tree

相当于有一些相等的限制,用连边表示,那么一个连通块内必须相等。

为了最优,不同连通块的值肯定不等,于是两个位置相同等价于连通块相同。于是我们只需要求出 dsu 再求答案就好了。

直接做肯定是 \(O(n^3)\) 的,不难发现这两个东西都可以递推,预处理路径后继即可,时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define per(i,r,l) for(int i=(r);i>=(l);i--)
#define repll(i,l,r) for(ll i=(l);i<=(r);i++)
#define perll(i,r,l) for(ll i=(r);i>=(l);i--)
#define pb push_back
#define ins insert
#define clr clear
using namespace std;
namespace ax_by_c{
typedef long long ll;
const int N=3005;
int n,dis[N][N],nxt[N][N];
vector<int>g[N];
bool f[N][N];
void dfs(int u,int fa,int rt,int d){
	dis[rt][u]=d;
	for(auto v:g[u]){
		if(v==fa)continue;
		nxt[rt][v]=u;
		dfs(v,u,rt,d+1);
	}
}
void ff(int x,int y){
	if(f[x][y])return ;
	f[x][y]=1;
	if(dis[x][y]>1)ff(nxt[y][x],nxt[x][y]);
}
struct DSU{
	struct node{
		int fa,sz;
	}a[N];
	void Init(int n){
		rep(i,1,n)a[i]={i,1};
	}
	int find(int x){
		if(a[x].fa==x)return x;
		return a[x].fa=find(a[x].fa);
	}
	bool meg(int x,int y){
		x=find(x),y=find(y);
		if(x==y)return 0;
		if(a[x].sz<a[y].sz)swap(x,y);
		a[y].fa=x;
		a[x].sz+=a[y].sz;
		return 1;
	}
}dsu;
bool mk[N][N],res[N][N];
bool G(int x,int y){
	if(mk[x][y])return res[x][y];
	mk[x][y]=1;
	if(dsu.find(x)==dsu.find(y)){
		if(dis[x][y]<=1)res[x][y]=1;
		else res[x][y]=G(nxt[y][x],nxt[x][y]);
	}
	return res[x][y];
}
void slv(){
	scanf("%d",&n);
	rep(_,1,n-1){
		int u,v;
		scanf("%d %d",&u,&v);
		g[u].pb(v);
		g[v].pb(u);
	}
	rep(i,1,n)dfs(i,-1,i,0);
	rep(x,1,n)rep(y,1,n){
		char t;
		scanf(" %c",&t);
		if(t=='1')ff(x,y);
	}
	dsu.Init(n);
	rep(x,1,n)rep(y,1,n)if(f[x][y])dsu.meg(x,y);
	int ans=0;
	rep(x,1,n)rep(y,1,n)if(G(x,y))ans++;
	printf("%d\n",ans);
}
void main(){
	int T=1;
	// int csid=0;scanf("%d",&csid);
    // scanf("%d",&T);
    while(T--)slv();
}
}
int main(){
	string __name="";
	if(__name!=""){
		freopen((__name+".in").c_str(),"r",stdin);
		freopen((__name+".out").c_str(),"w",stdout);
	}
	ax_by_c::main();
	return 0;
}

E - Monotone OR

\(f_S\) 为变成 \(S\) 的方案数,有 \(f_0=1,f_S=\sum_{x\mid a_i=(S-1)}f_x\)

\(g_S=f_{S+1}\),则 \(f_S=g_{S-1},g_S=\sum_{x\mid a_i=S}f_x\)

容斥一下就能变成 \(g_S=(\sum_{x\subseteq S}[a_i=x])(\sum_{x\subseteq S}f_x)-\sum_{x\subsetneq S}g_x\)

这是半在线高维前缀和的形式,时间复杂度 \(O(n2^n)\)

半在线高维前缀和:

考虑分治,每次先计算左半边,然后计算左半边对右半边的贡献,最后计算右半边。

但是这样会有问题,左半部分的贡献到了右边后可能还会产生多次贡献。

于是利用左半边前缀和确定右半边值后,在右半边内部计算,计算完再计算左半边对右半边前缀和的贡献即可。

结合代码可能更好理解。

#include<bits/stdc++.h>
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define per(i,r,l) for(int i=(r);i>=(l);i--)
#define repll(i,l,r) for(ll i=(l);i<=(r);i++)
#define perll(i,r,l) for(ll i=(r);i>=(l);i--)
#define pb push_back
#define ins insert
#define clr clear
using namespace std;
namespace ax_by_c{
typedef long long ll;
const ll mod=998244353;
const int N=30;
const int S=(1<<24)+5;
int n,m,msk,cnt[S];
ll f[S],g[S],fs[S],gs[S];
void cdq(int l,int r){
	if(l==r){
		fs[l]=(fs[l]+f[l])%mod;
		f[l+1]=g[l]=(fs[l]*cnt[l]%mod-gs[l]+mod)%mod;
		fs[l]=f[l];
		gs[l]=g[l];
		return ;
	}
	int mid=l+((r-l)>>1);
	cdq(l,mid);
	for(int x=l,y=mid+1;x<=mid;x++,y++){
		fs[y]=(fs[y]+fs[x])%mod;
		gs[y]=(gs[y]+gs[x])%mod;
	}
	cdq(mid+1,r);
	for(int x=l,y=mid+1;x<=mid;x++,y++){
		fs[y]=(fs[y]+fs[x])%mod;
		gs[y]=(gs[y]+gs[x])%mod;
	}
}
void slv(int _csid,int _csi){
	scanf("%d %d",&n,&m);msk=(1<<n)-1;
	rep(i,1,m){
		int x;
		scanf("%d",&x);
		cnt[x]++;
	}
	rep(j,0,n-1)rep(i,0,msk)if(i&(1<<j))cnt[i]+=cnt[i^(1<<j)];
	f[0]=1;
	cdq(0,msk);
	printf("%lld\n",f[msk+1]);
}
void main(){
	int T=1,csid=0;
//	scanf("%d",&csid);
//	scanf("%d",&T);
	rep(i,1,T)slv(csid,i);
}
}
int main(){
	string __name="";
	if(__name!=""){
		freopen((__name+".in").c_str(),"r",stdin);
		freopen((__name+".out").c_str(),"w",stdout);
	}
	ax_by_c::main();
	return 0;
}
posted @ 2025-05-30 07:54  ax_by_c  阅读(24)  评论(0)    收藏  举报