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