[SDOI2006]保安站岗 洛谷p2458

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/MrTinTin/article/details/83536183

题目描述

五一来临,某地下超市为了便于疏通和指挥密集的人员和车辆,以免造成超市内的混乱和拥挤,准备临时从外单位调用部分保安来维持交通秩序。

已知整个地下超市的所有通道呈一棵树的形状;某些通道之间可以互相望见。总经理要求所有通道的每个端点(树的顶点)都要有人全天候看守,在不同的通道端点安排保安所需的费用不同。

一个保安一旦站在某个通道的其中一个端点,那么他除了能看守住他所站的那个端点,也能看到这个通道的另一个端点,所以一个保安可能同时能看守住多个端点(树的结点),因此没有必要在每个通道的端点都安排保安。

编程任务:

请你帮助超市经理策划安排,在能看守全部通道端点的前提下,使得花费的经费最少。

输入输出格式

输入格式:

第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

(以下全部都是对于要覆盖任意一个以x为根的子树来说的)
(其中我们设y节点为y的儿子,fa为x的父亲)
1.x节点被自己覆盖,即选择x点来覆盖x点

2.x节点被儿子y覆盖,即选择y点来覆盖x点

3.x节点被父亲fa覆盖,即选择fa点来覆盖x点

借此三种状态,我们可以设f[x][0/1/2]为让以x为根的子树中的节点全部被覆盖,且x点的被覆盖情况为1/2/3时的最小代价

为了方便,我们不妨设这三种情况分别为:

1.f[x][0]---对应上面的1

2.f[x][1]---对应上面的2

3.f[x][2]---对应上面的3

既然是DP,总是有转移方程的,我们想一下dp方程要如何设计

设计状态转移方程:

(1):对应上面的1.

f[x][0]=∑ min(f[y][0],f[y][1],f[y][2]) + val[x]

其中val[x]是选择x点的代价

我们很容易想到,在节点x被选择之后,我们就可以无拘无束了(蛤?),也就是说对于x儿子节点y的状态可以不去考虑,因为x节点被选择之后y节点无论如何也会被覆盖到,所以我们在儿子y的所有状态里取min,累加起来就行了

(2):对应上面的3(先讲3,因为2比较难以理解,放到了后面)

f[x][2]=∑ min(f[y][0],f[y][1])

为什么3情况对应的转移方程要这样写呢?

我们不妨这样理解,对于x节点我们让它的父亲节点fa覆盖它,那么根据我们的状态设计,此时必须要满足以x的儿子y为根的子树之中所有点已经被覆盖

那么这时就转化为一个子问题要让y子树满足条件只有两种决策:要么y被y的儿子覆盖,要么被y自己覆盖(即选择y节点),只需要在y的这两种状态取min累加就可以了

(3):对应上面的2(DuangDuangDuang 敲黑板划重点啦)

f[x][1]=∑ min(f[y][0],f[y][1]),如果选择的全部都是f[y][1],要再加上min(f[y][0]-f[y][1])

这又是什么意思呢?真是让人摸不着头发,,,质壁分离(逃

到了这里,我们就要回顾一下我们设计的dp状态了:

设f[x][0/1/2]为让以x为根的子树中的节点全部被覆盖,且x点的被覆盖情况为1/2/3时的最小代价

先提示一下,如果你理解了下面,那么本题是很简单的。。如果你没理解,就返回到这里再看一遍吧,我就在这里等着你

咳咳。。说正经的。。(逃

对于此时的状态,f[x][1]代表对于节点x让x被自己的儿子覆盖,那么和分析(2)一样,都要先满足此时以y的子树已经满足了条件,才能进行转移,这就是前面那部分:∑ min(f[y][0],f[y][1])的来历,那么后面那一长串又是怎么回事呢?

我们可以这样理解,此时既然要保证x点是被自己的儿子覆盖的,那么如果此时y子树已经满足了全部被覆盖,但是y此时被覆盖的状态却是通过y节点自己的儿子达到的,那么x就没有被儿子y覆盖到,那么我们不妨推广一下,如果x所有的儿子y所做的决策都不是通过选择y点来满足条件,那么我们就必须要选择x的一个子节点y,其中y满足f[y][0]-f[y][1]最小,并把这个最小的差值累加到f[x][1]中去这样才能使得x点被自己的儿子覆盖,状态f[x][1]也才能合理地得到转移

好了,如果你还是没有太懂这个(3)的设计过程,请你回到之前再仔细看几遍

如果你已经理解了上面,那么恭喜你这个题,你已经A掉了

因为转移方程既然有了,那么我们就只需要最后的答案了

由于题目中没有说这棵树的根节点是哪个,所以你可以默认1就是根,或者开一个数组在加边的时候记录一下每个点的入度,最后没有入度的点就是根(但好像没有区别,毕竟我A掉了)

最后答案即为min(f[root][0],f[root][1])

因为根节点没有父亲,所以不需要考虑它的f[root][2]状态

那这样的花。。下面就放一下我丑陋的代码好了(逃

还请dalao们不喜勿喷

PS:代码里也有解释,希望能帮到你更深地理解一下本题

#include<bits/stdc++.h>
#define f(i,l,r) for(i=(l);i<=(r);i++)
using namespace std;
const int MAXN=3005,INF=100000000;
struct Edge{
    int v,nxt;
}e[MAXN<<1];
int f[3][MAXN<<1],h[MAXN<<1],tot,n;
int a[MAXN<<1];
inline void add(int u,int v)
{
    e[tot].v=v;
    e[tot].nxt=h[u];
    h[u]=tot++;
}
void dfs(int u,int fa)
{
    int i,flag=0,min_las=INF;
    f[0][u]=a[u];
    for(i=h[u];~i;i=e[i].nxt){
        int v=e[i].v;
        if(v==fa) continue;
        dfs(v,u);
        f[0][u]+=min(f[0][v],min(f[1][v],f[2][v]));
        f[2][u]+=min(f[0][v],f[1][v]);
        if(f[0][v]<f[1][v]) flag=1;
        else min_las=min(min_las,f[0][v]-f[1][v]);
        f[1][u]+=min(f[0][v],f[1][v]);
    }
    if(!flag) f[1][u]+=min_las;
}
int main()
{
    ios::sync_with_stdio(false);
    memset(h,-1,sizeof(h));
    int i,j,k,u,v;
    cin>>n;
    f(i,1,n){
        cin>>u;
		cin>>a[u]>>k;
        f(j,1,k){
            cin>>v;
            add(u,v);
            add(v,u);
        }
    }
    dfs(1,-1);
    cout<<min(f[0][1],f[1][1])<<endl;
    return 0;
 }

猜你喜欢

转载自blog.csdn.net/MrTinTin/article/details/83536183
今日推荐