题意:
给定一个树,选择若干点,使得选择的结点中任一结点不会和它的子结点同时选择,求能选结点最大数量。同时判断方案数是否为一。
思路:树的最大独立集,用树形dp,dfs一遍找每个结点的父亲,是为了从下向上刷每个结点儿子的最大独立集和and每个结点孙子的最大独立集和的表
判断方案数是否唯一同样在树形dp的同时递推判断即可
算法复杂度可以是线性
//248K 0MS C++
#include<cstdio>
#include<iostream>
#include<cstring>
#include<iostream>
#include<map>
#include<string>
#include<vector>
using namespace std;
int n;
bool flag;
const int N = 220;
map<string,int >name;
struct Edge
{
int v;
Edge(int _v=0) :v(_v){};
};
vector<Edge> es[N];
int dp[N];
int fa[N]; //每个节点的父亲
int dep[N]; //每个结点的深度
int sum_s[N]; //每个结点儿子的最大独立集和
bool viss[N],visgs[N],visdp[N]; //分别标记对应结点的dp值,孙子的最大独立集和,儿子的最大独立集和 他们是否名单唯一
int sum_gs[N]; //每个结点孙子的最大独立集和
int mdep;
void dfs(int u,int pa)
{
if(pa==-1) dep[u]=0;
else dep[u] = dep[pa]+1;
mdep=max(mdep,dep[u]);
int sz=es[u].size();
for(int i=0;i<sz;i++)
{
int v=es[u][i].v;
if(v!=pa) dfs(v,fa[v]=u);
}
}
void ini()
{
flag=0;
mdep=-1;
name.clear();
for(int i=0;i<n;i++) es[i].clear();
memset(sum_gs,0,sizeof(sum_gs));
memset(sum_s,0,sizeof(sum_s));
memset(viss,0,sizeof(viss));
memset(visgs,0,sizeof(visgs));
memset(visdp,0,sizeof(visdp));
}
int main()
{
while(scanf("%d",&n),n)
{
ini();
int sz=0;
string empl,boos;
cin>>boos;
if(!name.count(boos))
name[boos]=sz++;
for(int i=0;i<n-1;i++)
{
cin>>empl>>boos;
if(!name.count(empl))
name[empl]=sz++;
if(!name.count(boos))
name[boos]=sz++;
int u=name[empl],v=name[boos];
es[u].push_back(Edge(v));
es[v].push_back(Edge(u));
}
dfs(0,-1);
for(int l=mdep;l>=0;l--)
for(int i=n-1;i>=0;i--)
{
if(dep[i]==l)
{
if(sum_s[i]>1+sum_gs[i])
{
dp[i]=sum_s[i];
visdp[i]=viss[i];
}
else if(sum_s[i]<1+sum_gs[i])
{
dp[i]=1+sum_gs[i];
visdp[i]=visgs[i];
}
else
{
dp[i]=sum_s[i];
visdp[i]=true;
}
if(l>=1)
{
sum_s[fa[i]] +=dp[i];
viss[fa[i]] |= visdp[i];
}
if(l>=2)
{
sum_gs[ fa[fa[i]] ] += dp[i];
visgs[fa[fa[i]]] |= visdp[i];
}
}
}
printf("%d ",dp[0]);
if(visdp[0]) puts("No");
else puts("Yes");
}
return 0;
}
上面的方法是入门经典中的,对于求每个结点d[i]都有两种决策
同样也可以增加状态从而减少决策d[i][0]为子树中不选i的最大独立集的状态,d[i][1]表示在i的子树中选了i的最大独立集状态,转移方程详见P283(紫书),值得一提的是这种细化状态的在树形dp中应用更广,状态转移更直接方便,不需像上面的刷表