警卫安排
Time Limit:10000MS Memory Limit:65536K
Total Submit:34 Accepted:18
Case Time Limit:1000MS
Description
一个重要的基地被分为n个连通的区域。出于某种神秘的原因,这些区域以一个区域为核心,呈一颗树形分布。
在每个区域安排警卫所需要的费用是不同的,而每个区域的警卫都可以望见其相邻的区域,只要一个区域被一个警卫望见或者是安排有警卫,这个区域就是安全的。你的任务是:在确保所有区域都是安全的情况下,找到安排警卫的最小费用。
Input
第一行n,表示树中结点的数目。
接下来的n行描述了n个区域的信息,每一行包含的整数依次为:区域的标号i(0<i<=n),在区域i安排警卫的费用k,区域i的子结点数目m,接下来m个数为区域i的子结点编号。
Output
一行一个整数,为最小的安排费用。
Sample Input
6
1 30 3 2 3 4
2 16 2 5 6
3 5 0
4 4 0
5 11 0
6 5 0
Sample Output
25
Hint
对于所有的数据,0<n<=720。
我们可以很显然的发现这是一道树形DP
我们讨论结点i:
i不安排警卫,i被父亲望到,这时i没有安排警卫,i的儿子要么安排警卫,要么被它的后代望到。
i不安排警卫,i被儿子望到,即i的某个儿子安排了警卫,其他儿子需要安排警卫或者被它的后代望到。
i安排了警卫,i的儿子结点被i望到,可能安排警卫,可能被它的后代看到。
状态:f[i][j]表示以i为根这棵子树按排警卫所需最小费用(其中i号节点采用的是第j种监视方式)
j代表监视方式,分为0,1,2三种
f[i][0]表示在第i号节点安排一个警卫,自己望,的最小费用
f[i][1]表示由i的儿子监视第i号位置,被儿子望,的最小费用
f[i][2]表示由i的父亲监视第i号位置 ,被父亲望,的最小费用
f[i][0]= min{ f[son[1]][0] , f[son[1]][1] , f[son[1]][2] }
+min{ f[son[2]][0] , f[son[2]][1] , f[son[2]][2] }
+......
+min{ f[son[k]][0] , f[son[k]][1] , f[son[k]][2] } + cost[i]
(i的儿子数为k)
f[i][2]= min{ f[son[1]][0] , f[son[1]][1] }+min{ f[son[2]][0] , f[son[2]][1]}
+......+min{ f[son[k]][0] , f[son[k]][1] }
至于f[I][1],我们思考:
f[i][1]是在i节点,有儿子望到i的情况,而f[i][2]是在i节点,他的父节点望到他的情况
对于f[i][1],i的所有儿子中一定有一个儿子自己安排了警卫,而对于f[i][2],i的儿子除了可以靠自己安排,也可以让自己的后代安排
因此,f[i][1]与f[i][2]满足关系f[i][1]=min{f[i][2]-min(f[s][0],f[s][1])+f[s][0]}
其中s为i的儿子
最后输出答案的时候,由于根节点没有父节点,所以输出min(f[root][0],f[root][1])
#include<cstdio>
#include<iostream>
using namespace std;
const int maxn=1500,inf=1e9;
int NEXT[maxn],END[maxn],LAST[maxn],n;
int cost[maxn],cnt,f[maxn][4];
bool mark[maxn];
void insert(int a,int b){
END[++cnt]=b;
NEXT[cnt]=LAST[a];
LAST[a]=cnt;
}
void DP(int p){
int i,j;
if(LAST[p]==0)f[p][0]=cost[p];f[p][1]=inf;return;}
for(i=LAST[p],j=END[i];i;i=NEXT[i],j=END[i])DP(j);
f[p][0]=cost[p];
f[p][1]=inf;
for(i=LAST[p],j=END[i];i;i=NEXT[i],j=END[i]){
f[p][0]+=min(f[j][0],min(f[j][1],f[j][2]));
f[p][2]+=min(f[j][0],f[j][1]);
}
for(i=LAST[p],j=END[i];i;i=NEXT[i],j=END[i])
f[p][1]=min(f[p][1],f[p][2]-min(f[j][0],f[j][1])+f[j][0]);
}
int main(){
int i,j,x,y,m,k;
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%d%d%d",&x,&k,&m);
cost[x]=k;
for(j=1;j<=m;j++){
scanf("%d",&y);
insert(x,y);
mark[y]=1;//mark记录当前节点是不是一个子节点
}
}
for(i=1;i<=n;i++)//找根节点
if(!mark[i]){k=i;break;}
DP(k);
cout<<min(f[k][0],f[k][1]);
}