NKOI 1472 警卫安排

警卫安排

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]);
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值