思路是覆盖子树,我们发现,农民想截住牛的最优策略是不断向上来尽可能地覆盖更大的子树
我们想要尽早地覆盖一个子树,一个显然的贪心是在这个子树中选取深度最小的一个放农民
如果我们在一个点放置了农民,那么其他点也会被覆盖,所有这个农民能够覆盖的叶子都不需要再放农民了
抽象出来,对于每个叶节点,都有一个深度,我们把叶节点按深度排序,然后每次选取深度最浅的点打标记,然后枚举每个点,把这个点能覆盖的点全部删去
那么我们怎样支持这样一种操作呢,我们可以用一个$set$,每次取出$s.begin$,考虑它是否能够覆盖点$x$,能够覆盖的前提是$d[begin]-d[lca]<=d[lca]$,即农民比奶牛先或同时走到$lca$
复杂度我不会证,如果有大佬看到可以帮忙证一下
#include<iostream> #include<cstdio> #include<cstring> #include<set> using namespace std; const int maxn=1e5+10,lg=18; struct edge{ int next,to; }e[maxn*2]; struct node{ int d,id; }; int n,k,head[maxn],cnt,ans,d[maxn],deg[maxn],sk[maxn][20]; set<node>s; bool operator <(const node &x,const node &y) { return x.d==y.d?x.id<y.id:x.d<y.d; } void add(int x,int y) { e[++cnt].next=head[x]; e[cnt].to=y; head[x]=cnt; } void make(int x,int pre) { sk[x][0]=pre,d[x]=d[pre]+1; if(deg[x]==1) s.insert((node){d[x],x}); for(int v,i=head[x];i;i=e[i].next) if((v=e[i].to)!=pre) make(v,x); } void equals(int &x,int h) { for(int i=0;h;i++) { if(h&1) x=sk[x][i]; h>>=1; } } int lca(int x,int y) { if(d[x]<d[y]) swap(x,y); equals(x,d[x]-d[y]); if(x==y) return x; for(int i=lg;i>=0;i--) if(sk[x][i]!=sk[y][i]) x=sk[x][i],y=sk[y][i]; return sk[x][0]; } int main() { scanf("%d%d",&n,&k); for(int x,y,i=1;i<n;i++) { scanf("%d%d",&x,&y); add(x,y),add(y,x); deg[x]++,deg[y]++; } if(deg[k]==1) { printf("1 "); return 0; } d[0]=-1; make(k,0); for(int i=1;i<=lg;i++) for(int j=1;j<=n;j++) sk[j][i]=sk[sk[j][i-1]][i-1]; while(!s.empty()) { node u=*s.begin(); ans++; s.erase(s.begin()); for(set<node>::iterator it=s.begin();it!=s.end();) { node tmp=*it; it++; if(d[u.id]<=2*d[lca(u.id,tmp.id)]) s.erase(tmp); } } printf("%d ",ans); return 0; }