题目求一棵树的最小支配数。
支配集,即把图的点分成两个集合,所有非支配集内的点都和支配集内的某一点相邻。
听说即使是二分图,最小支配集的求解也是还没多项式算法的。而树上求最小支配集树型DP就OK了。
树上的每个结点作为其子树的根可以有三个状态:
- 不属于支配集且还没被支配
- 不属于支配集但被其孩子支配
- 属于支配集
那么就是用dp[u][1\2\3]来作为动归的状态,表示结点u为根子树的且u状态为1、2、3的最小支配数。
123转移该怎么转移就怎么转移。。最后的结果就是min(dp[root][2],dp[root][3])。
要注意的是对于有些结点前2个状态可能是不存在的,比如叶子结点不存在第2个状态、存在孩子是叶子结点的结点不存在第1个状态,这些不存在的状态要在转移的时候处理。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define INF 123456
#define MAXN 111111
struct Edge{
int u,v,next;
}edge[MAXN<<1];
int NE,head[MAXN];
void addEdge(int u,int v){
edge[NE].u=u; edge[NE].v=v; edge[NE].next=head[u];
head[u]=NE++;
}
int d[MAXN][3];
int dp(int u,int k,int fa){
if(d[u][k]!=-1) return d[u][k];
int res=0,diff=INF; bool flag=0,isLeaf=1;
for(int i=head[u]; i!=-1; i=edge[i].next){
int v=edge[i].v;
if(v==fa) continue;
isLeaf=0;
if(k==0){
if(dp(v,1,u)==INF) return d[u][k]=INF;
res+=dp(v,1,u);
}else if(k==1){
if(dp(v,2,u)<=dp(v,1,u)){
res+=dp(v,2,u);
flag=1;
}else{
if(dp(v,1,u)==INF) return d[u][k]=INF;
res+=dp(v,1,u);
diff=min(diff,dp(v,2,u)-dp(v,1,u));
}
}else{
res+=min(min(dp(v,0,u),dp(v,1,u)),dp(v,2,u));
}
}
if(k==1 && isLeaf) return d[u][k]=INF;
if(k==1 && !flag) res+=diff;
return d[u][k]=res+(k==2);
}
int main(){
int n,a,b;
scanf("%d",&n);
NE=0;
memset(head,-1,sizeof(head));
for(int i=1; i<n; ++i){
scanf("%d%d",&a,&b);
addEdge(a,b); addEdge(b,a);
}
memset(d,-1,sizeof(head));
printf("%d",min(dp(1,1,0),dp(1,2,0)));
return 0;
}
/**
dp[i][0] = sum(min(dp[s][2],dp[s][1]))自己没被选,父节点被选中
dp[i][1] = sum(min(dp[s][1],dp[s][2]))自己没被选,某儿子结点被选中
dp[i][2] = sum(min(dp[s][0],dp[s][1],dp[s][2]))自己被选中
求dp[i][1] 时需注意当所有的dp[s][1] < dp[s][2]时是不满足条件的。
*/
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <cmath>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define L(i) i<<1
#define R(i) i<<1|1
#define INF 0x3f3f3f3f
#define pi acos(-1.0)
#define eps 1e-9
#define maxn 100010
#define MOD 1000000007
struct Edge
{
int to,next;
} edge[maxn<<1];
int n,dp[maxn][3];
int tot,head[maxn];
void add(int u,int v)
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}
void dfs(int u,int pre)
{
dp[u][0] = 0;
dp[u][1] = 0;
dp[u][2] = 1;
int flag = 0,Min = INF,cnt = 0,k = 0;
for(int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if(v == pre)
continue;
k = 1;
dfs(v,u);
dp[u][0] += min(dp[v][2],dp[v][1]);
dp[u][2] += min(dp[v][0],min(dp[v][1],dp[v][2]));
if(dp[v][1] < dp[v][2])
{
dp[u][1] += dp[v][1];
if(Min > dp[v][2])
{
Min = dp[v][2];
cnt = dp[v][1];
}
}
else
{
flag = 1;
dp[u][1] += dp[v][2];
}
}
if(!k)
{
dp[u][1] = INF;
return;
}
if(!flag)
dp[u][1] += Min - cnt;
}
int main()
{
int t,C = 1;
//scanf("%d",&t);
while(scanf("%d",&n) != EOF)
{
int a,b;
tot = 0;
memset(head,-1,sizeof(head));
for(int i = 1; i < n; i++)
{
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
memset(dp,-1,sizeof(dp));
dfs(1,-1);
printf("%d\n",min(dp[1][1],dp[1][2]));
}
return 0;
}