这个提出的真心非常好!YM!
题意:
给定一棵无根树,问至少需要添加多少条边,使得每个节点属于且仅属于一个圈,并且,每个圈的节点数至少为3。若无解则输出-1,否则输出至少添加的边数。
题解:
如果不知道这个是TreeDP的话或者我大概会往图论方面想的,求SCC啊之类的。
自己YY的时候没有注意到,如果是两个子链合成一个圈的话,那么根节点一定是属于该圈,于是一直在郁闷怎么分类讨论若干种转移,毕竟这种写法还是太奇葩了。
一开始我的状态设计是:
- f[0, u]表示根节点u属于某个圈,且以u为根的整棵子树都符合要求,所需要添加的最少边数。
- f[1, u]表示根节点u属于某条链,且以u为根的整棵子树除了该链之外都符合要求,所需要添加的最少边数。
然后再用一个tag[]来表示这条链的长度是否大于1。= = 但,很不幸地,它WA了。而且无论是随机生成数据对拍亦或者是构造数据,都是FC: no differences encountered,应该是哪个小细节没写对吧,于是只能把这种非主流写法改成主流的写法啦。>_<
- f[0, x]表示以x为根的树,变成每个顶点恰好在一个圈中的图,需要添加的最少边数。
- f[1, x]表示以x为根的树,除了根x以外,其余节点变成每个节点恰好在一个圈中的图,需要添加的最少边数。
- f[2, x]表示以x为根的树,除了根x以及其所在的一条链(*链长度要大于1)以外,其余节点变成每个节点恰好在一个圈中的图,需要添加的最少边数。
于是就有:
i,j,v∈thesetofx′sson,i≠j
f[0, x] = min{
sum{f[0, v]} – max{f[0, i] – f[2, i]} ,
sum{f[0, v]} – max{f[0, i] – min{f[1, i], f[2, i]} – max{f[0, j] – min{f[1, j], f[2, j]}}
}
f[1, x] = sum{f[0, v]}
f[2, x] = sum{f[0, v]} – max{f[0, i] – min{f[2, i], f[1, i]}}
或者参考Felicia的solution:
A.根R的所有子树自己解决(取状态0),转移到R的状态1。即R所有的儿子都变成每个顶点恰好在一个环中的图,R自己不变。
B.根R的k-1个棵树自己解决,剩下一棵子树取状态1和状态2的最小值,转移到R的状态2。剩下的那棵子树和根R就构成了长度至少为2的一条链。
C.根R的k-2棵子树自己解决,剩下两棵子树取状态1和状态2的最小值,在这两棵子树之间连一条边,转移到R的状态0。
D.根R的k-1棵子树自己解决,剩下一棵子树取状态2(子树里还剩下长度至少为2的一条链),在这棵子树和根之间连一条边,构成一个环,转移到R的状态0。
(上文引用自:http://abyss.ylen.me/archives/4)
由于一看这题有环就没思路,我是看题解的,看完了就后悔了,这个完全可以自己想到的!我该死。。
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <iostream> 5 6 #define N 10000 7 8 using namespace std; 9 10 int to[N],next[N],head[N],cnt,n,dp[N][3]; 11 12 inline void add(int u,int v) 13 { 14 to[cnt]=v; next[cnt]=head[u]; head[u]=cnt++; 15 } 16 17 void read() 18 { 19 memset(head,-1,sizeof head);cnt=0; 20 for(int i=1,a,b;i<n;i++) 21 { 22 scanf("%d%d",&a,&b); 23 add(a,b); add(b,a); 24 } 25 } 26 27 void find(int u,int fa) 28 { 29 int sum=0; 30 for(int i=head[u];~i;i=next[i]) 31 if(to[i]!=fa) 32 { 33 find(to[i],u); 34 sum+=dp[to[i]][0]; 35 } 36 dp[u][1]=sum; 37 for(int i=head[u];~i;i=next[i]) 38 if(to[i]!=fa) 39 { 40 dp[u][0]=min(dp[u][0],sum-dp[to[i]][0]+dp[to[i]][2]+1); 41 dp[u][2]=min(dp[u][2],sum-dp[to[i]][0]+min(dp[to[i]][1],dp[to[i]][2])); 42 for(int j=head[u];~j;j=next[j]) 43 if(to[j]!=fa&&to[i]!=to[j]) 44 dp[u][0]=min(dp[u][0],sum-dp[to[i]][0]-dp[to[j]][0]+min(dp[to[i]][1],dp[to[i]][2])+min(dp[to[j]][1],dp[to[j]][2])+1); 45 } 46 } 47 48 void go() 49 { 50 for(int i=1;i<=n;i++) dp[i][0]=dp[i][1]=dp[i][2]=999999; 51 find(1,-1); 52 if(dp[1][0]>99999) dp[1][0]=-1; 53 printf("%d\n",dp[1][0]); 54 } 55 56 int main() 57 { 58 while(scanf("%d",&n)!=EOF) 59 { 60 read(); 61 go(); 62 } 63 return 0; 64 }