原题:https://www.luogu.org/problem/P2279ios
题解转载自:https://www.luogu.org/blog/contributation/solution-p2279数组
找最低没被覆盖到的点,并在它的祖父处设一个消防站。考虑到这个点的全部子孙后代都已经被覆盖了,所以这时覆盖祖父能盖到更多额外的点,并保证结果不会更差。spa
不少思路是用dfs或堆求取最低节点,实际上不必,只要预处理出深度(边输入边处理)并排序,碰到已覆盖就跳过,未覆盖就在祖父处设消防站,ans++。blog
问题在于怎样才能判断这个点覆盖到了没有。对于儿子或孙子覆盖他,能够在在儿子处设站时就标记它;而对于父亲和祖父覆盖他,能够用儿子对父亲的映射f来解决;问题在于兄弟。其实,能够用o数组维护“离i最近的消防站到i的距离”,当o[父亲]==1时,就能肯定它是否被覆盖。排序
代码it
#include<iostream> #include<cstdio> #include<algorithm> #define N 2020 #define FOR(i,a,b) for(int i=a;i<=b;i++) using namespace std; int n,b[N],f[N],d[N],o[N],ans,u,v,w; bool cmp(int x,int y){return d[x]>d[y];} int main(){ scanf("%d",&n);b[1]=1,o[1]=o[0]=N; FOR(i,2,n) scanf("%d",&f[i]),d[i]=d[f[i]]+1,b[i]=i,o[i]=N; sort(b+1,b+n+1,cmp); FOR(i,1,n){ v=b[i],w=f[v],u=f[f[v]]; o[v]=min(o[v],min(o[w]+1,o[u]+2)); if(o[v]>2){ o[u]=0,ans++; o[f[u]]=min(o[f[u]],1),o[f[f[u]]]=min(o[f[f[u]]],2); } }printf("%d",ans); }
这种方法的普适性很强,能够解决半径为k的最小覆盖问题。并且不用存图。只须要把维护“父亲和爷爷”改为维护“上位k位祖先”便可,复杂度O(N*K),常数也很小。io