POJ3763 Tour in Wonder Land

一道很不错的树状DP题,题意是求向一颗树,无向的,中加最少的边使其有哈密顿回路。

刚开始以为是张普通的图,还想着那样也可以树状DP呢。主要的想法是将一个回路拆开来看,想是一条链,然后先求一颗树中加最少的边,记做K,使得其有一条哈密顿链,然后最后再向起点和终点加一条边,答案就是K+1。并且对于这样分析子树哈密顿链的方法,来扩展想要的状态的答案。对于这样分析,子树链的状态有两种情况,先给定一个方向,即有个根,那么一种是以根为起点,终止于一个非根节点,记做dp[i][0],另一种状态是以一个非根节点为起点,另一个非根节点为终点,记做dp[i][1]。对于这样的状态设计,可以进行父节点与子节点间的状态转移。但对于这样的状态设计,转移起来,比较麻烦。因此把第一种状态归并到第二种状态中更新为最新的第二种状态。
因此可以这样转移:
dp[u][0] = min{dp[v][0] + dp[1..son(u) except for v][1] + number(son) - 1}
dp[u][1] = min{dp[v][0] + dp[w][0] + dp[1..son(u) except for v & w][1] + number(son) - 2}
dp[u][1] = min{dp[u][0], dp[u][1]}
对于上述的状态转移,其实在dp[u][1]中,还有别的状态可能转移过来,但这里可以通过数学归纳法和普通到特殊的方法来证明这样的状态是不会比之前设计的状态转移过来所更优(但经过实验发现是错的)。可以从贪心的角度看,第一种的转态转移理解成,必须走根到儿子的一条边,要不浪费了,第二种情况同理。
树状深搜时,可以通过向dfs中添加父标记来操作。可以通过累加dp[son][1]的和来简化状态转移时的操作。
感谢:
http://blog.csdn.net/yuhailin060/archive/2010/05/10/5576255.aspx
http://blog.csdn.net/ccsu_001/archive/2010/05/10/5576386.aspx

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
#include < iostream >
#include
< string >
#include
< vector >
using namespace std;

const int MAX = 100005 ;

int n;
int dp[MAX][ 2 ];

vector
< vector < int > > mm;

void dfs( int u, int f)
{
dp[u][
0 ] = dp[u][ 1 ] = - 1 ;
int sz = mm[u].size();
int son = 0 , all = 0 ;
for ( int i = 0 ; i < sz; i ++ )
{
int v = mm[u][i];
if (v != f)
{
dfs(v, u);
all
+= dp[v][ 1 ];
son
++ ;
}
}
if (son == 0 ) dp[u][ 0 ] = dp[u][ 1 ] = dp[u][ 2 ];
else
{
// 说明不是叶子
// get dp[u][0]
for ( int i = 0 ; i < sz; i ++ )
{
int v = mm[u][i];
if (v != f)
{
int t = dp[v][ 0 ] + all - dp[v][ 1 ] + son - 1 ;
if (dp[u][ 0 ] == - 1 || t < dp[u][ 0 ])
dp[u][
0 ] = t;
}
}
// get dp[u][1]
for ( int i = 0 ; i < sz; i ++ ) for ( int j = i + 1 ; j < sz; j ++ )
{
int v = mm[u][i], w = mm[u][j];
if (v != f && w != f)
{
int t = dp[v][ 0 ] + dp[w][ 0 ] + all - dp[v][ 1 ] - dp[w][ 1 ] + son - 2 ;
if (dp[u][ 1 ] == - 1 || t < dp[u][ 1 ])
dp[u][
1 ] = t;
}
}
if (dp[u][ 1 ] == - 1 || dp[u][ 0 ] < dp[u][ 1 ])
dp[u][
1 ] = dp[u][ 0 ];
}
// s[u] = 1;
}

int main()
{
while (scanf( " %d " , & n) != EOF)
{
mm.clear();
mm.resize(n
+ 1 );
for ( int i = 1 ; i < n; i ++ )
{
int a, b;
scanf(
" %d%d " , & a, & b);
mm[a].push_back(b);
mm[b].push_back(a);
}
dfs(
1 , - 1 );
printf(
" %d\n " , dp[ 1 ][ 1 ] + 1 );
}
}

 

 

转载于:https://www.cnblogs.com/litstrong/archive/2010/07/27/1786415.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值