码迷,mamicode.com
首页 > 其他好文 > 详细

100130. 【USACO 2018 January Platinum】鱼池逃脱Cow at Large

时间:2020-05-02 23:17:02      阅读:95      评论:0      收藏:0      [点我收藏+]

标签:cas   stat   解决   tin   efi   +=   clu   定义   ast   

题目

地图是一棵无根树,pty从某个点出发,每次可以往某一条边走,“逃离”定义为可以在不遇到怪(点或边上相遇)的情况下,到达叶子节点。
若干个叶子节点上一开始可以放若干个怪,每次这些怪都可以往某一边走。
问对于每个点,最少要放多少怪才能保证抓住pty。
\(n \leq 70000\)
实际上可以做\(n\leq 1000000\)


思考历程

只知道暴力怎么做,但是没有部分分,所以也没有去打。
考虑固定起点之后的答案:
将起点作为根;
对于每个叶子,找到它和起点之间的中点(如果在边上就取儿子)。这意味着如果选了这个叶子,那么这个点的子树都可以被覆盖。
选择最少的叶子节点,让所有的叶子节点都被覆盖。
于是在每个叶子和起点的中点上打标记,贪心地取。
\(O(n^2)\)


正解

正解是个很NB的转化。
题解将上面“叶子和起点的中点”,定义为“控制点”。
选择的叶子节点个数,等于选择的控制点个数,等于位于控制点的子树个数。
可以发现贪心地选择后,覆盖的点相当于将所有控制点的子树合并起来,那么答案就是连通块个数。
用一种NB的方法表示子树个数:
显然,对于一棵子树\(S\)而言,\(\sum_{v\in S}deg_v=2|S|-1\)
移项得\(\sum_{v \in S}{2-deg_v}=1\)
于是我们对每个位于控制点下方(或控制点本身)的点\(v\),求\(2-deg_v\)的和,就得到了子树个数,也就是答案。

然后考虑根节点变化的情况。
设当前根节点为\(x\)
对于某个点\(y\),先预处理出离\(y\)最近的叶子节点(不一定要子树内)到\(y\)的距离\(mnd_y\)
\(y\)对答案有贡献,当且仅当\(mnd_y\leq dis(x,y)\)

直接点分治,假如\(x\)\(y\)分属\(root\)的两个不同子树,那么\(y\)\(x\)有贡献就要满足\(mnd_y-dep_y\leq dep_x\)。直接树状数组解决之,\(O(n\lg^2n)\)

其实还有更加优秀的做法。贡献分子树和子树的补集计算,子树随便搞,子树的补集就换根,拿个数据结构(好像树状数组就够了)随便维护一下。\(O(n \lg n)\)
当然由于换根看上去不如点分治好写,所以我没有写。


代码

点分治。

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#define N 70010
#define INF 1000000000
int n;
struct EDGE{
	int to;
	EDGE *las;
	bool bz;
} e[N*2];
int ne;
EDGE *last[N];
#define rev(ei) (e+(int((ei)-e)^1))
int deg[N];
int mnd[N];
void bfs(){
	memset(mnd,255,sizeof mnd);
	static int q[N];
	int tail=0;
	for (int i=1;i<=n;++i)
		if (deg[i]==1){
			mnd[i]=0;
			q[++tail]=i;
		}
	for (int i=1;i<=tail;++i){
		int x=q[i];
		for (EDGE *ei=last[x];ei;ei=ei->las)
			if (mnd[ei->to]==-1){
				mnd[ei->to]=mnd[x]+1;
				q[++tail]=ei->to;
			}
	}
}
int siz[N],all;
void getsiz(int x,int fa){
	siz[x]=1;
	for (EDGE *ei=last[x];ei;ei=ei->las)
		if (ei->to!=fa && ei->bz==0){
			getsiz(ei->to,x);
			siz[x]+=siz[ei->to];
		}
}
int getG(int x,int fa){
	bool is=all-siz[x]<=all>>1;
	for (EDGE *ei=last[x];ei;ei=ei->las)
		if (ei->to!=fa && ei->bz==0){
			int t=getG(ei->to,x);
			if (t)
				return t;
			is&=siz[ei->to]<=all>>1;
		}
	return is?x:0;
}
int dep[N];
void init(int x,int fa){
	for (EDGE *ei=last[x];ei;ei=ei->las)
		if (ei->to!=fa && ei->bz==0){
			dep[ei->to]=dep[x]+1;
			init(ei->to,x);
		}
}
int lis[N],k;
int t[N];
void clear(int n){memset(t,0,sizeof(int)*(n+2));}
void add(int x,int c){++x;for (;x-1<=all;x+=x&-x) t[x]+=c;}
int query(int x){++x;int r=0;for (;x;x-=x&-x) r+=t[x];return r;}
int ans[N];
void calc(int x,int fa){
	if (deg[x]!=1)
		ans[x]+=query(dep[x]);
	for (EDGE *ei=last[x];ei;ei=ei->las)
		if (ei->to!=fa && ei->bz==0)
			calc(ei->to,x);
}
void insert(int x,int fa){
	add(max(mnd[x]-dep[x],0),2-deg[x]);
	for (EDGE *ei=last[x];ei;ei=ei->las)
		if (ei->to!=fa && ei->bz==0)
			insert(ei->to,x);
}
void divide(int x){
	getsiz(x,0);
	all=siz[x],x=getG(x,0);
	dep[x]=0,init(x,0);
	k=0;
	for (EDGE *ei=last[x];ei;ei=ei->las)
		if (ei->bz==0)
			lis[++k]=ei->to;
	clear(all);
	for (int i=1;i<=k;++i){
		int y=lis[i];
		calc(y,x);
		insert(y,x);
	}
	if (deg[x]!=1)
		ans[x]+=query(dep[x]);
	clear(all);
	for (int i=k;i>=1;--i){
		int y=lis[i];
		add(max(mnd[x]-dep[x],0),2-deg[x]);
		calc(y,x);
		add(max(mnd[x]-dep[x],0),-(2-deg[x]));
		insert(y,x);
	}
	for (EDGE *ei=last[x];ei;ei=ei->las)
		if (ei->bz==0){
			ei->bz=rev(ei)->bz=1;
			divide(ei->to);
		}
}
int main(){
	scanf("%d",&n);
	for (int i=1;i<n;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		e[ne]={v,last[u],0};
		last[u]=e+ne++;
		e[ne]={u,last[v],0};
		last[v]=e+ne++;
		deg[u]++,deg[v]++;
	}
	bfs();
	for (int i=1;i<=n;++i)
		if (deg[i]==1)
			ans[i]=1;
	divide(1);
	for (int i=1;i<=n;++i)
		printf("%d\n",ans[i]);
	return 0;
}

总结

真是个神仙的转化思想……
我感觉我可以称其为“度数-子树反演”。
感觉这种神仙东西要靠积累啊,考场真的很难想……

100130. 【USACO 2018 January Platinum】鱼池逃脱Cow at Large

标签:cas   stat   解决   tin   efi   +=   clu   定义   ast   

原文地址:https://www.cnblogs.com/jz-597/p/12819794.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!