人,只有自己站起来,这个世界才能属于他。|

园龄:粉丝:关注:

题解:The Game (Easy Version & Hard Version)

前言

这是最近 VP CF 遇到的。

感觉是套着博弈壳子的树上 DS,做起来思路也很自然,于是记录之。

思路分析

E1

经过手玩样例发现,对于 x,如果存在 y 不在 x 子树内且 wy>wx,此时 wx 最大的 x 一定是必胜点。

原因是,这样的 x 满足,无论后手操作任意 y,先手都无法再次操作。

考虑用反证法证明。

假设操作完 x 后,依然存在两点 y,z,使得后手操作 y 后,先手依然可以操作 z,不难发现 wy<wz

考虑分类讨论:

  • 如果 y,z 存在子孙关系,那么只能是 yz 的祖先,否则 x 不是 wx 最大的点,y 才是。而如果 yz 的祖先,那么操作完 y 后无法再操作 z,矛盾;

  • 如果 y,z 不存在子孙关系,那么 y 一定可以取代 x 成为点权最大的点,矛盾。

所以,我们的结论是正确的。

实现上,可以用树状数组记录不同点权点的出现次数,在 dfs 的过程中差分即可。

总体复杂度 O(nlogn)

E2

感觉是一个题,会 E1 就会 E2。

根据 E1 的结论,当前局面是必败局面当且仅当存在一个点 x,使得 x 子树外的点 y 满足 wx<wy

也就是说,只要在先手操作后依然存在一个合法的 x,他必败。

为了获胜,他必须将当前局面变成必败局面,也就是使得图上不存在上述合法的 x

然后,考虑哪些点满足在操作完它之后,当前局面是必败局面。

不难发现,这样的 x,对于任意 wy>wx,应当满足:

  1. xy 的祖先;

  2. z 表示满足点权 >wy 且不在 y 子树内的点的共同 lca,xz 的祖先。

两条满足任意一个即可。也很好理解,要么 y 不存在,要么让后手选 y 之后必输。

然后按点权从大到小加入点,用 dfs 序线段树维护每个 y 对应的 z,再用一个树状数组做树上差分即可。

总体复杂度 O(nlog2n),常数很小,可以通过。

代码实现

E1

#include<bits/stdc++.h>
#define int long long
using namespace std;
int t,n,x,y,ans,a[400005],b[400005],c[400005],dfn[400005];
int head[400005],nxt[800005],target[800005],tot;
void add(int x,int y){
	tot++;
	nxt[tot]=head[x];
	head[x]=tot;
	target[tot]=y;
}
bool cmp(int x,int y){
	return a[x]>a[y];
}
#define lowbit(i) (i&(-i))
void modify(int x,int k){
	for(int i=x;i<=n;i+=lowbit(i)){
		c[i]+=k;
	}
}
int query(int x){
	int ans=0;
	for(int i=x;i;i-=lowbit(i)){
		ans+=c[i];
	}
	return ans;
}
void dfs(int x,int fa){
	int now=query(n)-query(a[x]);
	modify(a[x],1);
	for(int i=head[x];i;i=nxt[i]){
		int y=target[i];
		if(y==fa) continue;
		dfs(y,x); 
	}
	if(b[x]==query(n)-query(a[x])-now) b[x]=0;
	else b[x]=1;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>t;
	while(t--){
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			modify(a[i],1);
			dfn[i]=i;
		}
		for(int i=1;i<=n;i++){
			b[i]=query(n)-query(a[i]);
		}
		for(int i=1;i<n;i++){
			cin>>x>>y;
			add(x,y);
			add(y,x);
		}
		dfs(1,0);
		sort(dfn+1,dfn+1+n,cmp);
		for(int i=1;i<=n;i++){
			if(b[dfn[i]]){
				ans=dfn[i];
				break;
			}
		}
		cout<<ans<<'\n';
		ans=tot=0;
		for(int i=1;i<=n;i++){
			head[i]=b[i]=c[i]=0;
		}
	}
	return 0;
}

E2

#include<bits/stdc++.h>
using namespace std;
int t,n,m,x,y,a[400005],num,tmp1,tmp2;
vector<int> ans,v[400005];
int head[400005],nxt[800005],target[800005],tot;
void add(int x,int y){
	tot++;
	nxt[tot]=head[x];
	head[x]=tot;
	target[tot]=y;
}
int siz[400005],dfn[400005],rnk[400005],hson[400005],top[400005],dep[400005],f[400005],cnt;
void dfs1(int x,int fa){
	siz[x]=1;
	for(int i=head[x];i;i=nxt[i]){
		int y=target[i];
		if(y==fa) continue;
		dep[y]=dep[x]+1;
		f[y]=x;
		dfs1(y,x);
		siz[x]+=siz[y];
		if(siz[hson[x]]<siz[y]) hson[x]=y;
	}
}
void dfs2(int x,int t){
	cnt++;
	dfn[x]=cnt;
	rnk[cnt]=x;
	top[x]=t;
	if(!hson[x]) return;
	dfs2(hson[x],t);
	for(int i=head[x];i;i=nxt[i]){
		int y=target[i];
		if(y==f[x] || y==hson[x]) continue;
		dfs2(y,y);
	}
}
int lca(int x,int y){
	if(!x) return y;
	if(!y) return x;
	while(top[x]^top[y]){
		if(dep[top[x]]>dep[top[y]]) x=f[top[x]];
		else y=f[top[y]];
	}
	if(dfn[x]<dfn[y]) return x;
	else return y;
}
int val[800005],ls[800005],rs[800005],dcnt,rt;
void build(int l,int r,int &x){
	x=++dcnt;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(l,mid,ls[x]);
	build(mid+1,r,rs[x]);
}
void modify(int l,int r,int pos,int x){
	if(l==r){
		val[x]=rnk[l];
		return;
	} 
	int mid=(l+r)>>1;
	if(pos<=mid) modify(l,mid,pos,ls[x]);
	else modify(mid+1,r,pos,rs[x]);
	val[x]=lca(val[ls[x]],val[rs[x]]);
}
int query(int l,int r,int ql,int qr,int x){
	if(ql<=l && r<=qr) return val[x];
	int mid=(l+r)>>1,ans=0;
	if(ql<=mid) ans=lca(ans,query(l,mid,ql,qr,ls[x]));
	if(qr>=mid+1) ans=lca(ans,query(mid+1,r,ql,qr,rs[x]));
	return ans;
}
int c[400005];
#define lowbit(i) (i&(-i))
void chg(int x,int k){
	if(!x) return;
	for(int i=x;i<=n;i+=lowbit(i)){
		c[i]+=k; 
	}
}
int get(int x){
	int ans=0;
	for(int i=x;i;i-=lowbit(i)){
		ans+=c[i];
	}
	return ans;
}
void init(){
	for(int i=1;i<=n;i++){
		v[i].clear();
		head[i]=dfn[i]=rnk[i]=siz[i]=hson[i]=top[i]=c[i]=0;
	}
	for(int i=1;i<=dcnt;i++){
		val[i]=ls[i]=rs[i]=0;
	}
	ans.clear();
	cnt=tot=num=dcnt=rt=0;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>t;
	while(t--){
		cin>>n;
		init();
		for(int i=1;i<=n;i++){
			cin>>a[i];
			v[a[i]].push_back(i);
		}
		for(int i=1;i<n;i++){
			cin>>x>>y;
			add(x,y);
			add(y,x);
		}
		dfs1(1,0);
		dfs2(1,1);
		build(1,n,rt);
		for(int i=n;i>=1;i--){
			for(int j=0;j<v[i].size();j++){
				x=v[i][j];
				if(dfn[x]==1) tmp1=0;
				else tmp1=query(1,n,1,dfn[x],rt);
				if(dfn[x]+siz[x]-1==n) tmp2=0;
				else tmp2=query(1,n,dfn[x]+siz[x],n,rt);
				y=lca(tmp1,tmp2);
				if(!y) continue;
				//cout<<"qq"<<x<<' '<<get(dfn[x]+siz[x]-1)<<' '<<get(dfn[x]-1)<<'\n';
				if(get(dfn[x]+siz[x]-1)-get(dfn[x]-1)==num) ans.push_back(x);
			}
			for(int j=0;j<v[i].size();j++){
				x=v[i][j];
				if(dfn[x]==1) tmp1=0;
				else tmp1=query(1,n,1,dfn[x],rt);
				if(dfn[x]+siz[x]-1==n) tmp2=0;
				else tmp2=query(1,n,dfn[x]+siz[x],n,rt);
				y=lca(tmp1,tmp2);
				//cout<<"qwq"<<x<<' '<<y<<'\n';
				if(y){
					num++;
					if(x==y){
						chg(dfn[x],1);
					}else{
						chg(dfn[x],1);
					    chg(dfn[y],1);
					    chg(dfn[lca(x,y)],-1);
					} 
				}
			}
			for(int j=0;j<v[i].size();j++){
				x=v[i][j];
				modify(1,n,dfn[x],rt);
			}
		}
		sort(ans.begin(),ans.end());
		cout<<ans.size()<<' ';
		for(int i=0;i<ans.size();i++){
			cout<<ans[i]<<' ';
		}
		cout<<'\n';
	}
	return 0;
}

总结

总体来说我很喜欢这道题,由 E1 的提示引出了 E2 的正解,在得出正确的博弈策略之后,还需要用恰当的方式刻画博弈策略,以方便后续的维护。这既考察了选手对于复杂博弈问题的分析与刻画能力,在实现时也需要比较扎实的维护树上信息的能力,我认为这也是这道题能被被评到 *3000 的主要原因。

本文作者:Kenma

本文链接:https://www.cnblogs.com/Kenma/p/18699705

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   _Kenma  阅读(38)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起