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

Posted jz-597

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了100130. USACO 2018 January Platinum鱼池逃脱Cow at Large相关的知识,希望对你有一定的参考价值。

题目

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


思考历程

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


正解

正解是个很NB的转化。
题解将上面“叶子和起点的中点”,定义为“控制点”。
选择的叶子节点个数,等于选择的控制点个数,等于位于控制点的子树个数。
可以发现贪心地选择后,覆盖的点相当于将所有控制点的子树合并起来,那么答案就是连通块个数。
用一种NB的方法表示子树个数:
显然,对于一棵子树(S)而言,(sum_{vin 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_yleq dis(x,y))

直接点分治,假如(x)(y)分属(root)的两个不同子树,那么(y)(x)有贡献就要满足(mnd_y-dep_yleq dep_x)。直接树状数组解决之,(O(nlg^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
",ans[i]);
	return 0;
}

总结

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

以上是关于100130. USACO 2018 January Platinum鱼池逃脱Cow at Large的主要内容,如果未能解决你的问题,请参考以下文章

Dr.Hardware.2010.v10.0.4d.German-BEAN

[USACO 2018 Jan Gold] Tutorial

5187: [Usaco2018 Jan]Sprinklers

[bzoj5278][Usaco2018 Open]Out of Sorts

[bzoj5483][Usaco2018 Dec]Balance Beam_凸包_概率期望

[USACO 2018 Open Gold] Tutorial