洛谷 P2279 03湖南 消防局的设立
2016-05-30 16:18:17
题目链接: 洛谷 P2279 03湖南 消防局的设立
题目大意:
给定一棵树,选定一个节点的集合,使得所有点都与集合中的点的距离在2以内
解法1:
贪心
首先DFS转化为有根树,每次取最深节点的爷爷进入集合
1 //消防局的设立 03湖南 2 //贪心 3 #include<stdio.h> 4 #include<algorithm> 5 using namespace std; 6 const int maxn=1010; 7 struct node 8 { 9 int deep; 10 int id; 11 }; 12 node f[maxn]; 13 int marked[maxn]; 14 int father[maxn]; 15 struct edge 16 { 17 int to; 18 int next; 19 edge(){} 20 edge(int to,int next):to(to),next(next){} 21 }; 22 edge n[maxn*2]; 23 int head[maxn]; 24 int cnt; 25 int N; 26 int ans; 27 void insert(int x,int y) 28 { 29 n[++cnt]=edge(y,head[x]); 30 head[x]=cnt; 31 n[++cnt]=edge(x,head[y]); 32 head[y]=cnt; 33 return ; 34 } 35 void DFS(int x,int depth) 36 { 37 f[x].deep=depth; 38 for(int i=head[x];i;i=n[i].next) 39 { 40 if(n[i].to!=father[x]) 41 { 42 father[n[i].to]=x; 43 DFS(n[i].to,depth+1); 44 } 45 } 46 return ; 47 } 48 bool comp(node a,node b) 49 { 50 return a.deep>b.deep; 51 } 52 void clear(int x,int len) 53 { 54 marked[x]=1; 55 if(len==2)return ; 56 for(int i=head[x];i;i=n[i].next) 57 { 58 clear(n[i].to,len+1); 59 } 60 return ; 61 } 62 int main() 63 { 64 scanf("%d",&N); 65 for(int i=2;i<=N;i++) 66 { 67 int x; 68 scanf("%d",&x); 69 insert(i,x); 70 } 71 for(int i=1;i<=N;i++) 72 { 73 f[i].id=i; 74 } 75 father[1]=1; 76 DFS(1,1); 77 sort(f+1,f+N+1,comp); 78 for(int i=1;i<=N;i++) 79 { 80 if(marked[f[i].id]==0) 81 { 82 clear(father[father[f[i].id]],0); 83 ans++; 84 } 85 } 86 printf("%d",ans); 87 }
解法2:
恶心的树状动归
方程很多,看了好多题解才有了一点点想法
DP[x][len]就表示以x为根的子树中距离根为len的点全部建立消防局的最小消耗数
同时我们规定在某一个点建立消防局的条件是这个点所染的点的子树(包括他自己)不存在不合法的点
为了方便:我们将DP[x][len]改写为min(DP[x][0.1.2.....len]),son表示x节点的儿子
现在来分析DP[x][0]
在根节点染色,最多影响到根节点的孙子.而要使孙子以下的节点都被染色,应该将3代,4代或者5代染色(即min(DP[son][2],DP[son][3],DP[son][4]))
又因为我们之前为了方便将DP[x][len]改写为了最小值,所以直接累加即可
(其中DP[son][0]和DP[son][1]并不会影响结果,因为浅的节点本身要染的点一定大于等于深的节点)
再来分析DP[x][3],DP[x][4](个人觉得这两个数组更像是用来辅助更新的,好像不应该放在DP数组里)
这个比较好理解,根的3代就是根的儿子的2代,DP[x][3]=sigma(DP[son][2]);
同样也容易得到,根的4代就是根的儿子的3代,DP[x][4]=sigma(DP[son][3]);
最后来看DP[x][2],DP[x][1];
DP[x][2],不难发现,我们选择一个孙子,根节点就会合法,但是根节点的其他侧枝就不一定了
所以我们优先将所有侧枝以最佳方案染掉,sigma(DP[son][2]),再选择孙子(+DP[son][1]),并去掉重复统计的数据(-DP[son][2]);
DP[x][1],不难发现,我们选择一个儿子,根节点和这个节点的兄弟就都合法了,但是根节点其他孙子们就不一定了
所以我们优先将所有的孙子们以最佳方案染掉,sigma(DP[son][3]),再选择儿子(+DP[son][0]),并去掉重复统计的数据(-DP[son][3]);
综上:方程为
DP[x][0]=sigma(DP[son][4])+1(本身建立的方案);
DP[x][1]=sigma(DP[son][3])-min(DP[son][3]-DP[son][0]);
DP[x][2]=sigma(DP[son][2])-min(DP[son][2]-DP[son][1]);
DP[x][3]=sigma(DP[son][2]);
DP[x][4]=sigma(DP[son][3]);
1 //消防局的设立 (03湖南) 2 //树状动归 3 #include<stdio.h> 4 #include<algorithm> 5 using namespace std; 6 const int maxn=1010; 7 struct edge 8 { 9 int to; 10 int next; 11 edge(){} 12 edge(int to,int next):to(to),next(next){} 13 }; 14 edge n[maxn*2]; 15 int N; 16 int DP[maxn][5]; 17 int cnt; 18 int head[maxn]; 19 int father[maxn]; 20 void insert(int x,int y) 21 { 22 n[++cnt]=edge(x,head[y]); 23 head[y]=cnt; 24 n[++cnt]=edge(y,head[x]); 25 head[x]=cnt; 26 return ; 27 } 28 void DFS(int x) 29 { 30 DP[x][1]=1e8; 31 DP[x][2]=1e8; 32 for(int i=head[x];i;i=n[i].next) 33 { 34 if(n[i].to==father[x])continue; 35 father[n[i].to]=x; 36 DFS(n[i].to); 37 DP[x][0]+=DP[n[i].to][4]; 38 DP[x][3]+=DP[n[i].to][2]; 39 DP[x][4]+=DP[n[i].to][3]; 40 } 41 DP[x][0]++; 42 for(int i=head[x];i;i=n[i].next) 43 { 44 if(n[i].to==father[x])continue; 45 DP[x][1]=min(DP[x][1],DP[x][4]-DP[n[i].to][3]+DP[n[i].to][0]); 46 DP[x][2]=min(DP[x][2],DP[x][3]-DP[n[i].to][2]+DP[n[i].to][1]); 47 } 48 for(int i=1;i<=4;i++) 49 { 50 DP[x][i]=min(DP[x][i],DP[x][i-1]); 51 } 52 return ; 53 } 54 int main() 55 { 56 scanf("%d",&N); 57 for(int i=2;i<=N;i++) 58 { 59 int x; 60 scanf("%d",&x); 61 insert(i,x); 62 } 63 DFS(1); 64 printf("%d",DP[1][2]); 65 }