luogu P4183 [USACO18JAN]Cow at Large P 题解
P4183 [USACO18JAN]Cow at Large P
题意
贝茜被农民们逼进了一个偏僻的农场。农场可视为一棵有 \(N\) 个结点的树,结点分别编号为 \(1,2,\ldots, N\) 。每个叶子结点都是出入口。开始时,每个出入口都可以放一个农民(也可以不放)。每个时刻,贝茜和农民都可以移动到相邻的一个结点。如果某一时刻农民与贝茜相遇了(在边上或点上均算),则贝茜将被抓住。抓捕过程中,农民们与贝茜均知道对方在哪个结点。
请问:对于结点 \(i\,(1\le i\le N)\) ,如果开始时贝茜在该结点,最少有多少农民,她才会被抓住。
题解
通过读题可以得出,农民于贝茜的关系一定是相遇而不是追及。
考虑最后被封锁的状态,一定是贝茜在某一个节点,并且这一个节点的儿子全部被农民所占领。
通过观察可以发现,一个农民如果往上移则可以封锁上移后节点的整个子树。
考虑贪心,我们希望一个农民可以封锁的节点尽可能多,也就是尽可能地往上走。
但是从农民的角度上不太好思考,因为我们无法确定上面有没有农民会封锁住当前所在的子树,或者说是不太方面于统计。
考虑哪一些点可以成为封锁点。
一个点的农民应该来自于离它最近的节点,设离\(i\)号点的最近的叶子点为\(g_i\)。
那一个节点能够被封锁当且仅当\(g_i \leq dep_i\),意思是农民能够在贝茜离开之前到达。
但是我们会发现,这样算出来的封锁点实际上是有多余的,因为上面的点可以封锁住下面的点,下面的点就可以不用选了。
并且容易发现,这些封锁点是一定会形成一个连通块的,不会出现断层的情况,会一直延申到叶子节点,这个也是比较好证明的。
这样我们就可以发先最上面那一个点的贡献其实可以看做一个联通块是1的贡献
然后下面就有一个比较神奇的构造。
一个点成为封锁点对答案的贡献为\(2-d_i\),\(d_i\)为每个点连接的边数。
这是一个什么原理呢?
设一个联通块有\(n\)点,则有\(n-1\)条边,则\(\sum d_i = 2n-2\),但是实际上还有一条出边,实际上会有\(2n-1\)条边。
因为贡献变为了\(2-d_i\),则一个联通块的贡献就变为了1,这样就不需要关心是否是最上面的一个点了,只需要将整个连通块的贡献加起来即可。
假设我们以\(u\)为根节点,那么\(ans_u=\sum[g_i \leq dep_{u,i}](2-d_i)\)。
容易发现这是一个点对贡献的问题,直接点分治即可。
#include <bits/stdc++.h>
#define pii pair<int,int>
#define mp make_pair
#define rg register
#define pc putchar
#define gc getchar
#define pf printf
#define space pc(' ')
#define enter pc('\n')
#define me(x,y) memset(x,y,sizeof(x))
#define pb push_back
#define FOR(i,k,t,p) for(rg int i(k) ; i <= t ; i += p)
#define ROF(i,k,t,p) for(rg int i(k) ; i >= t ; i -= p)
using namespace std ;
bool s_gnd ;
const int N = 1e5+5 ;
const int INF = 1e9+7 ;
int n,rt,sum,now ;
int sz[N],mx[N],dis[N],ans[N],d[N],g[N],vis[N] ;
vector<int>e[N],kkk,kkkk ;
vector<pii>k,kk ;
inline void read(){}
template<typename T,typename ...T_>
inline void read(T &x,T_&...p)
{
x = 0 ;rg int f(0) ; rg char c(gc()) ;
while(!isdigit(c)) f |= (c=='-'),c = gc() ;
while(isdigit(c)) x = (x<<1)+(x<<3)+(c^48),c = gc() ;
x = (f?-x:x) ;
read(p...);
}
int stk[30],tp ;
inline void print(){}
template<typename T,typename ...T_>
inline void print(T x,T_...p)
{
if(x < 0) pc('-'),x = -x ;
do stk[++tp] = x%10,x /= 10 ; while(x) ;
while(tp) pc(stk[tp--]^48) ; space ;
print(p...) ;
}
bool S_GND ;
void Dfs1(int x)
{
vis[x] = 1,g[x] = INF ;
if(d[x] == 1) g[x] = 0 ;
for(auto v:e[x]) if(!vis[v])
Dfs1(v),g[x] = min(g[v]+1,g[x]) ;
}
void Dfs2(int x)
{
vis[x] = 1 ;
for(auto v:e[x]) if(!vis[v])
g[v] = min(g[v],g[x]+1),Dfs2(v) ;
}
void Get_sz(int x,int fa)
{
sz[x] = 1 ;
for(auto v:e[x]) if(v != fa && !vis[v])
Get_sz(v,x),sz[x] += sz[v] ;
}
void Get_rt(int x,int fa)
{
mx[x] = 0 ;
for(auto v:e[x]) if(v != fa && !vis[v])
Get_rt(v,x),mx[x] = max(mx[x],sz[v]) ;
mx[x] = max(mx[x],sum-sz[x]) ;
if(mx[x] <= sum/2) rt = x ;
}
void dfs(int x,int fa)
{
kkk.pb(x),kkkk.pb(x) ; if(0 >= g[x]-dis[x]) ans[now] += 2-d[x] ;
k.pb(mp(g[x]-dis[x],2-d[x])),kk.pb(mp(g[x]-dis[x],2-d[x])) ;
for(auto v:e[x]) if(v != fa && !vis[v])
dis[v] = dis[x]+1,dfs(v,x) ;
}
int cmp(int x,int y)
{
return dis[x] < dis[y] ;
}
void calc(int x)
{
kkkk.clear(),k.clear(),now = x,dis[x] = 0 ;
k.pb(mp(g[x]-dis[x],2-d[x])) ;
for(auto v:e[x]) if(!vis[v])
{
dis[v] = 1 ;
kk.clear(),kkk.clear(),dfs(v,x) ; int sum = 0,top = 0 ;
sort(kk.begin(),kk.end()),sort(kkk.begin(),kkk.end(),cmp) ;
for(auto r:kkk)
{
while(top < kk.size() && kk[top].first <= dis[r]) sum += kk[top].second,++top ;
ans[r] -= sum ;
}
}
sort(k.begin(),k.end()) ; int sum = 0,top = 0 ;
sort(kkkk.begin(),kkkk.end(),cmp) ;
for(auto r:kkkk)
{
while(top < k.size() && k[top].first <= dis[r]) sum += k[top].second,++top ;
ans[r] += sum ;
}
}
void Solve(int x)
{
vis[x] = 1,calc(x) ;
for(auto v:e[x]) if(!vis[v])
Get_sz(v,x),sum = sz[v],rt = 0,Get_rt(v,x),Solve(rt) ;
}
signed main()
{
//cerr<<(double)(&s_gnd-&S_GND)/1024.0/1024.0 ;
// freopen(".in","r",stdin) ;
// freopen(".out","w",stdout) ;
read(n) ;
FOR(i,2,n,1)
{
int u,v ; read(u,v) ;
e[u].pb(v),e[v].pb(u),d[u]++,d[v]++ ;
}
Dfs1(1),me(vis,0),Dfs2(1),me(vis,0),Get_sz(1,1),sum = n,Get_rt(1,1),Solve(rt) ;
FOR(i,1,n,1) print(ans[i]),enter ;
return 0 ;
}