CF Round 1001 题解合集

here.

草怎么是贪心专场。

问就是不会 F。

C

注意到操作一至多只会进行一次,进行两次就抵消了。

所以直接枚举所有可能操作取最大值即可。

注意要开 long long。

总体复杂度 \(O(tn^3)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
int t,n,ans,a[51],b[51],c[51];
void get(int cnt){
	memcpy(c,b,sizeof(c));
	for(int i=1;i<=n-cnt;i++){
		b[i]=c[i+1]-c[i];
	}
}
int solve(int now){
	memcpy(b,a,sizeof(b));
	int sum=0,ans=0;
	for(int i=1;i<=n;i++){
		ans+=b[i];
	}
	int cnt=1;
	while(cnt<n){
	    if(now==cnt) reverse(b+1,b+1+n-cnt+1);
		get(cnt); 
		sum=0;
		for(int i=1;i<=n-cnt;i++){
			sum+=b[i];
		}
		ans=max(ans,sum);
		cnt++;
	}
	return ans;
}
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];
			ans+=a[i];
		}
		for(int i=0;i<n;i++){
			ans=max(ans,solve(i));
		}
		cout<<ans<<'\n';
		ans=0;
	}
	return 0;
}

D

草感觉比 E 困难啊。

为了方便,我们强制每次操作的 \((u,v)\) 相连。

首先钦定 \(1\) 为根,假设 \(a\) 是已知的,不难发现对于每次操作 \((u,v)\),它们是相互独立的,只影响 \((u,v)\) 两点的相对点权。

想要最小化点权,那么对于每个点,应该这样操作:

  • 如果 \(a_x < a_{fa}\),那么令 \(u=fa,v=x\),使得 \(a_x \to a_{fa}\)

  • 如果 \(a_x > a_{fa}\),那么令 \(u=x,v=fa\),使得 \(a_{fa} \to x\)

不难发现,这样操作后,点权为 \(\sum_x \max(0,a_x-a_{fa})\)。此处认为 \(1\) 的父亲是 \(0\)

所以,我们的问题转化为最小化 \(\sum_x \max(0,a_x-a_{fa})\)

不难发现,在没有区间限制的前提下,令 \(a_x \to \max_{y\in son_x}(a_y)\) 是最优的。

加上区间限制之后,结论依然成立。

注意对于叶子节点来说,令 \(a_x \to l_x\) 是最优的。

按题意模拟贪心策略,总体复杂度 \(O(n)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
int t,n,maxn,l[200005],r[200005],x,y;
int head[200005],nxt[400005],target[400005],tot;
void add(int x,int y){
	tot++;
	nxt[tot]=head[x];
	head[x]=tot;
	target[tot]=y;
}
int f[200005],a[200005];
void dfs(int x,int fa){
	f[x]=fa;
	bool flag=true;
	for(int i=head[x];i;i=nxt[i]){
		int y=target[i];
		if(y==fa) continue;
		dfs(y,x);
		a[x]=max(a[x],max(l[x],a[y]));
		flag=false;
	}
	if(flag) a[x]=l[x];
	a[x]=min(a[x],r[x]);
}
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>>l[i]>>r[i];
		}
		for(int i=1;i<n;i++){
			cin>>x>>y;
			add(x,y);
			add(y,x);
		}
		dfs(1,0);
		for(int i=1;i<=n;i++){
			maxn+=max(0ll,a[i]-a[f[i]]);
		}
		cout<<maxn<<'\n';
		for(int i=1;i<=n;i++){
			head[i]=0;
			a[i]=0;
			f[i]=0;
		}
		tot=maxn=0;
	}
}

E1

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

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

考虑用反证法证明。

假设操作完 \(x\) 后,依然存在两点 \(y,z\),使得后手操作 \(y\) 后,先手依然可以操作 \(z\),不难发现 \(w_y<w_z\)

考虑分类讨论:

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

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

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

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

总体复杂度 \(O(n \log n)\)

#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

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

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

所以,对于先手来说,他只能一步取胜,也就是一步将局面变成必败局面。

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

不难发现,这样的 \(x\),对于任意 \(w_y>w_x\),应当满足:

  1. \(x\)\(y\) 的祖先;

  2. \(z\) 表示满足点权 \(>w_y\) 且不在 \(y\) 子树内的点的共同 lca,\(x\)\(z\) 的祖先。

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

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

总体复杂度 \(O(n \log^2 n)\),常数很小,可以通过。

#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;
}
posted @ 2025-02-02 19:23  _Kenma  阅读(75)  评论(0)    收藏  举报