题目描述
五一来临,某地下超市为了便于疏通和指挥密集的人员和车辆,以免造成超市内的混乱和拥挤,准备临时从外单位调用部分保安来维持交通秩序。
已知整个地下超市的所有通道呈一棵树的形状;某些通道之间可以互相望见。总经理要求所有通道的每个端点(树的顶点)都要有人全天候看守,在不同的通道端点安排保安所需的费用不同。
一个保安一旦站在某个通道的其中一个端点,那么他除了能看守住他所站的那个端点,也能看到这个通道的另一个端点,所以一个保安可能同时能看守住多个端点(树的结点),因此没有必要在每个通道的端点都安排保安。
编程任务:
请你帮助超市经理策划安排,在能看守全部通道端点的前提下,使得花费的经费最少。
输入格式
第1行 n,表示树中结点的数目。
第2行至第n+1行,每行描述每个通道端点的信息,依次为:该结点标号i(0<i<=n),在该结点安置保安所需的经费k(<=10000),该边的儿子数m,接下来m个数,分别是这个节点的m个儿子的标号r1,r2,...,rm。
对于一个n(0 < n <= 1500)个结点的树,结点标号在1到n之间,且标号不重复。
输出格式
最少的经费。
如右图的输入数据示例
输出数据示例:
输入输出样例
输入 #1复制
6 1 30 3 2 3 4 2 16 2 5 6 3 5 0 4 4 0 5 11 0 6 5 0
输出 #1复制
25
说明/提示
样例说明:在结点2,3,4安置3个保安能看守所有的6个结点,需要的经费最小:25
思路
我感觉和P2899 手机网络是一模一样的题目......相比之下,只需要改2处代码......(双倍经验)对于P2899那道题,我已经写了详细的题解。P2899的题解:这篇文章。
首先对于每个节点i,都有如下3种选择:
不选自己,选儿子;
不选自己,选父亲;
我自己选我自己。
令dp[i][0/1/2]为节点i及其节点i的子树中全部被覆盖所需的最小保安数,dp[i][0]表示点i有保安,dp[i][1]为点i没有保安,也就是就是父亲有信号塔,dp[i][2]表示节点i被间接管辖,也就是儿子有保安。
1.dp[i][0]
因为节点i有保安,所以对于i的子节点son,它可以没有保安,也可以有保安,也可以从son的子节点转移过来。因此:
dp[i][0]=∑min(dp[son][1],dp[son][0],dp[son][2])+val[i]
这里+val[i]是因为自己本身放置了一个保安,产生的代价。
2.dp[i][1]
因为节点i没有保安,靠的是节点i的父亲才被覆盖的,所以对于节点i的子节点son是不可能选父亲的。所以dp[i][1]可以从dp[son][0]转移过来(子节点有保安),也可以从dp[son][2]转移过来(子节点的儿子有保安)。
dp[i][1]=∑min(dp[son][0[,dp[son][2])
3.dp[i][2]
因为节点i没有保安,靠它儿子,所以节点i的子节点son也不可能选父亲,必然可以从dp[son][0]转移过来,也可以从dp[son][2]转移过来。
dp[i][2]=∑min(dp[son][0],dp[son][2])
很快会发现,如果的确是这样的话,如果dp[i][2]全从dp[son][2]转移过来,也就是若恒有dp[son][2] ≤ dp[son][0],就意味着节点i的所有子节点都没有保安!(那不就凉了)怎么办?
所以我们要设立一个反悔机制。我只需要一个儿子选。只需要用p来记录每一次dp[son][0]-min(dp[son][2],dp[son][0]),这样就保证,就算恒有dp[son][2] ≤ dp[son][0],也一定有一个是儿子选了的,如果不选dp[son][2],最后p的状态也为0。若不信,我们做个推导:
记p=min(p,dp[son][0]-min(dp[son][0],dp[son][2]))
dp[i][2]=min(dp[son][0],dp[son][2])+p
=min(dp[son][0],dp[son][2])+dp[son][0]-min(dp[son][0],dp[son][2])
若dp[son][2] ≤ dp[son][0]恒成立(注意我说的是恒成立,意思是说dp[i][2]全是从dp[son][2]转移过来)
原式=dp[son][2]+dp[son][0]-dp[son][2]
=dp[son][0]
也就是把dp[son][2]强制转换成了dp[son][0]!
若dp[son][2]>dp[son][0]
原式=dp[son][0]+dp[son][0]-dp[son][0]
=dp[son][0]
也就是说并不影响dp[son][0]的正常取值!
最后答案为min(dp[root][0],dp[root][2])。
#include <stdio.h>
#include <iostream>
#define inf 2e9+7
#define N 2001
using namespace std;
int n,m,s,dp[N][3],head[N],cnt,val[N];
struct node
{
int nxt,to;
}e[N<<1];
inline void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void dfs(int u,int fa)
{
register int i,p(inf);
dp[u][0]=val[u];
for(i=head[u];i;i=e[i].nxt)
{
int v(e[i].to);
if(v==fa) continue;
dfs(v,u);
dp[u][0]+=min(dp[v][0],min(dp[v][1],dp[v][2]));
dp[u][1]+=min(dp[v][0],dp[v][2]);
dp[u][2]+=min(dp[v][0],dp[v][2]);
p=min(p,dp[v][0]-min(dp[v][2],dp[v][0]));//表示其它儿子的总和
}
dp[u][2]+=p;//建立反悔机制
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
register int i,j,k;
cin>>n;
for(i=1;i<=n;i++)
{
int u,v;
cin>>u;
cin>>val[u]>>m;
while(m--)
{
cin>>v;
add(u,v);
add(v,u);
}
}
dfs(1,-1);
cout<<min(dp[1][0],dp[1][2])<<endl;
return 0;
}