题意:
给出一棵树(n<=1e5),求最少添加多少条边,使得能得到一条遍历所有点的哈密顿路径。(原题说是从点1出发,遍历每个点一遍,并且回到点1)
这个题目又给我上了一课…
训练赛的时候想了2个小时,最后放弃了… 感觉自己想到了其中一部分,还差一部分。
首先这个题目要考虑的是一个环,容易想到先考虑链,最后加1。这个基本想到了。
之后基于链考虑就有两种状态,一种是从根结点出发,一种是非根结点出发。这两个状态确实都想到了,最后在这里纠结了半天,最后弃疗了,因为没有处理好这个两个状态之间的关系,另辟蹊径地去想状态转移方程,最后炸了。之前一直在考虑,从非根节点出发的结果应该是从根结点出发的结果-1或者不减,然后分情况想了半天,然后对于单一子结点的情况和多个子结点的情况去分析把人搞晕了,最后实在不好转移…
dp[x][0]表示从根结点出发,整个路径形成一条链面,所需添加的最小边数。
dp[x][1]表示从非根节点出发,整个路径形成一条链面,所需添加的最小边数。
dp[x][0]=min{dp[u][0]+∑v is a son of xv!=udp[v][1]+son(x)−1}
dp[x][1]=min{dp[u][0]+dp[v][0]+∑w≠u,w≠vdp[w][1]+son(x)−2}
(为什么要这样,因为根结点x必须遍历,那一定是要到某个叶子结点,因为不从x出发,所以必须是从某个叶子结点到另一个叶子结点。还有一点就是叶子结点到根到叶子结点可以想象为根到两个叶子结点,这个用dp[][0]表示是等价的)
如果没有两个子结点,令dp[x][1]=dp[x][0]
其实你可以发现dp[x][1]<=dp[x][0]
最后考虑形成一个环,答案=dp[x][1]+1
可能会有疑问:那么出发点可能不是从1出发?
因为行成的是一个环,所以出发点就无所谓了,即使不是从1出发,那么现在就从1出发,还是走刚才的路线,所需添加的边不变。这就是由链到环的变化。
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define all(x) (x).begin(), (x).end()
#define for0(a, n) for (int (a) = 0; (a) < (n); (a)++)
#define for1(a, n) for (int (a) = 1; (a) <= (n); (a)++)
#define mes(a,x,s) memset(a,x,(s)*sizeof a[0])
#define mem(a,x) memset(a,x,sizeof a)
#define ysk(x) (1<<(x))
typedef long long ll;
typedef pair<int, int> pii;
const int INF =0x3f3f3f3f;
const int maxn= 100000 ;
int n;
vector<int >G[maxn+4];
int dp[maxn+4][2];
void update(int &x,int val) { if(x<0||val<x) x=val; }
void dfs(int x,int fa)
{
int siz=G[x].size(),son=0;
if(siz==1) { dp[x][0]=dp[x][1]=0;return; }
int ret=0;dp[x][0]=dp[x][1]=-1;
for0(i,siz) if(G[x][i]!=fa)
{
int y=G[x][i];dfs(y,x);son++;
ret+=dp[y][1];
}//update dp[x][0]
for0(i,siz) if(G[x][i]!=fa)
{
int y=G[x][i];
update(dp[x][0], ret-dp[y][1]+dp[y][0]+son-1 );
}//then update dp[x][1]
for0(i,siz) if(G[x][i]!=fa)
{ int y0=G[x][i];
for(int j=i+1;j<siz;j++) if(G[x][j]!=fa)
{ int y1=G[x][j];
update(dp[x][1], ret-dp[y0][1]-dp[y1][1]+dp[y0][0]+dp[y1][0]+son-2 );
}
}
if(dp[x][1]==-1) dp[x][1]=dp[x][0];
}
int main()
{
int x,y;
while(~scanf("%d",&n))
{
for1(i,n) G[i].clear();
for0(i,n-1)
{
scanf("%d%d",&x,&y);
G[x].push_back(y);G[y].push_back(x);
}
dfs(1,-1);
printf("%d\n",dp[1][1]+1);
}
return 0;
}
这个题有个重要的考虑就是对于结点x,子节点y为根的子树内所有结点的遍历应该是连续的。
还有就是dp[x][1]小于等于dp[x][0]。