题意:在一棵树上,当前节点i只能监视到下一个节点,如果有多个节点直接连接i,可以同时监视,问最少需要放多少士兵监视所有的节点。
思路:很明显,对于任一节点i,只有两种情况,放或者不放,所以定义dp[i][0]表示i节点不放士兵,把以i为根节点的整棵子树全部监视所需的最少士兵,dp[i][1]即放士兵所需的最小士兵。
设当前节点为i,i的直接子节点为j:
dp[i][0]+=dp[j][1] (当前不选,那么每棵子树必须得放,累加即可)
dp[i][1]+=min(dp[j][0],dp[j][1]);dp[i][1]+=1; (当前选了,子树选不选都可以,所有子树情况累加结束后,dp[i][1]再加1)
最后dp[root][0]与dp[root][1]比较取最小即为最优解。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<string>
#include<map>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#define ll long long
#define mst(a,k) memset(a,k,sizeof(a))
using namespace std;
vector<ll>v[2020];
ll n,f[2020],dp[2020][10];
void dfs(ll now)
{
if(v[now].size()==0) //判断是否是叶子节点,并将叶子的dp值直接初始化
{
dp[now][0]=0;
dp[now][1]=1;
return ;
}
for(ll i=0;i<v[now].size();i++)
{
ll nex=v[now][i];
dfs(nex);
dp[now][0]+=dp[nex][1]; //当前不放时
dp[now][1]+=min(dp[nex][0],dp[nex][1]); //当前放时
}
dp[now][1]+=1; //放士兵时,在子节点累加过后,加上一个1
}
int main()
{
char s[110];
while(~scanf("%lld",&n))
{
for(ll i=0;i<=n;i++)v[i].clear();
for(ll i=0;i<=n;i++)f[i]=i;
for(ll i=1;i<=n;i++)
{
scanf("%s",s+1);
ll len=strlen(s+1);
ll x1=0,x2=0,x3,d=1,pos=len-1;
for(;pos>=1;pos--)
{
if(s[pos]=='('){pos-=2;break;}
x1+=(s[pos]-'0')*d;
d*=10;
}
d=1;
for(;pos>=1;pos--)
{
x2+=(s[pos]-'0')*d;
d*=10;
}
for(ll j=1;j<=x1;j++)
{
scanf("%lld",&x3);
v[x2].push_back(x3);
f[x3]=x2; //保存根节点
}
}
ll root=1;
while(f[root]!=root)root=f[root]; //找出根节点
mst(dp,0);
dfs(root);
ll ans=min(dp[root][0],dp[root][1]);
printf("%lld\n",ans);
}
return 0;
}