标签: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