<JZOJ5944>信标
emmm树形dp?好像是的
搬一个题解证明过来
由于在n>1时答案至少为1,我们枚举一个必须放的根, 所有深度不同的点就被区分开了.
设一个节点有c个儿子, 发现必须在其中至少c−1个儿子的子树中放置信标.
证明如下: 考虑如果不这样放, 对于两棵都没有放的子树, 他们汇集到lca上以后距离都是相等的, 所以lca外的信标无法区分, 而内部没有信标. 所以不能存在两颗子树都不放. 所以至少要放c-1个. 由于在根节点放置了信标, 可以只考虑深度相同的点. 由于深度相同, 所以他们的lca度数至少为2,那么一定有一个信标在lca包含这两个点的两支子树中. 那么另一侧的点肯定要走更远的路, 会被区分开. 所以放c−1个足够区分.
这样问题变成每个节点要有c−1棵子树放有信标, 求最小方案. 直接贪心即可.
由于枚举根所 以复杂度为O(n2), 可以获得70分.
如何做到O(n)?
我们先特判链的情况答案为1,然后找到任意一个度数大于2的节点,可以证明这个点一定不需要放置信标. 于是以这个点作根O(n)的贪心即可.
证明如下:
深度相同的点对证明同上,只考虑深度不同的点对.如果它们在一颗子树中,由于度数大于2所以一定有另一颗子树的一个信标把他们区分开.
如果在不同的子树中, 有两种情况:
一个在没放信标的子树中,一个在放了的子树中.显然还存在另一个子树放了信标,由于深度不同他们会被这个信标区分开.
两个都在放了信标的子树中. 如果根的度数大于3则同上. 度数等于3时, 如果他们没有被区分开,一定是他们先汇集到了一个节点上, 然后走到同一个信标上. 这个点一定是一条奇链的中点, 且 不是根 (由于深度不同), 是在两个子树之一中唯一的. 那么他们走到另一个信标就一定有一个点走 了冤枉路, 既另一个信标可以区分出他们.
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #define rint register int template <class T>inline void read(T &X) { X=0;int W=0;char ch=0; while(!isdigit(ch))W|=ch=='-',ch=getchar(); while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar(); X=W?-X:X;return; } int n,head[1000010],cnt=0,count[1000010],f[1000010]; struct node{int to,next;}edge[2000010]; void add(int u,int v) { edge[++cnt].to=v; edge[cnt].next=head[u]; head[u]=cnt; } void dfs(int now,int fa) { int num=0,tot=0; for(rint i=head[now];i;i=edge[i].next) { int to=edge[i].to; if(to==fa)continue; dfs(to,now); tot+=f[to]; if(!f[to])num++; } f[now]=tot; if(num>1)f[now]+=(num-1); return; } int main() { //freopen("beacon.in","r",stdin); //freopen("beacon.out","w",stdout); read(n); if(n==1){printf("0\n");return 0;} for(rint i=1;i<n;++i) { int u,v; read(u),read(v); add(u,v),add(v,u); ++count[u],++count[v]; } int sta=-1; for(rint i=1;i<=n;++i)if(count[i]>=3)sta=i; if(sta==-1){printf("1\n");return 0;} dfs(sta,-1); printf("%d\n",f[sta]); return 0; }