NOI 警位安排(树形DP)

该博客介绍了如何使用树形动态规划(Tree DP)解决NOI中的一个警卫安排问题,旨在在确保所有区域安全的同时,最小化总费用。博主详细解释了输入输出格式,并给出了状态转移方程及相应的DFS实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/*
【题目描述】

    一个重要的基地被分成了n个连通的区域,出于某种原因,这个基地以某一个区域为核心,呈一树形分布。

    在每个区域里安排警卫的费用是不同的,而每个区域的警卫都可以望见其相邻的区域。如果一个区域有警卫或是被相邻区域的警卫望见,那它就是安全的,你的任务是:在确保所有的区域安全的状态下,使总费用最小。

【输入格式】

    第一行n,表示树中结点的数目。

    接下来n行,每行依次是:区域的编号;在此安排警卫的费用;它的子结点的个数m,然后往后m个数,为它的子结点编号。

【输出格式】

    一行一个数,为最小费用。

【输入样例】

6

1 30 3 2 3 4

2 16 2 5 6

3 5 0

4 4 0

5 11 0

6 5 0

【输出样例】

25



    对于每个点i,都有3种状态分别为:
要么在父亲结点安排警卫,即被父亲看到
要么在儿子结点安排警卫,即被儿子看到
要么安排警卫
对于i
i被父亲看到,这时i没有安排警卫,i的儿子要么安排警卫,要么被它的后代看到。
i被儿子看到,即i的某个儿子安排了警卫,其他儿子需要安排警卫或者被它的后代看到。
i安排了警卫,i的儿子可能还需要安排警卫,这样可能有更便易的方式照顾到它的后代;
所以i的儿子结点被i看到,可能安排警卫,可能被它的后代看到。
设f(i,0)表示i结点被父亲看到;
          f(i,1)表示i被它的儿子看到;
	     f(i,2)表示在i安排警卫;
则状态转移方程为:
     f[i][0]=∑min(f[x][2],f[x][1]);
    f[i][1]=∑min(f[x][2],f[x][1])+cost[i],但最后还要确保其中要有至少一个f[x][2];
    f[i][2]=∑min(f[x][0],f[x][1],f[x][2]);
    剩下的就跟普通的树形DP一样了。
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define MAXSIZE 105000
#define sf scanf
#define pf printf
#define __int64 long long
#define INF 0xfffffff
using namespace std;
int dp[MAXSIZE][3],cost[MAXSIZE],vis[MAXSIZE];
vector<int> tree[MAXSIZE];
void DFS(int r)
{
    for(int i=0;i<tree[r].size();i++)
    {
        DFS(tree[r][i]);
    }

        dp[r][2]+=cost[r];
    for(int i=0;i<tree[r].size();i++)
    {
        dp[r][0]+=min(dp[tree[r][i]][1],dp[tree[r][i]][2]);
        dp[r][1]+=min(dp[tree[r][i]][1],dp[tree[r][i]][2]);
        dp[r][2]+=min(min(dp[tree[r][i]][0],dp[tree[r][i]][1]),dp[tree[r][i]][2]);
    }
    //cout<<dp[r][0]<<" "<<dp[r][1]<<" "<<dp[r][2]<<endl;
    int temp=0xfffffff;
    for(int i=0;i<tree[r].size();i++)
    {
        temp=min(temp,dp[tree[r][i]][2]+dp[r][1]-min(dp[tree[r][i]][1],dp[tree[r][i]][2]));
    }
    dp[r][1]=temp;
    return ;
}
int main()
{
    freopen("in.txt","r",stdin);
    int u,v,n,m,C;
    while(~sf("%d",&n))
    {
        for(int i=0;i<=n;i++)
        {
            tree[i].clear();
            vis[i]=0;
        }
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
        {
            sf("%d%d%d",&u,&C,&m);
            cost[u]=C;
            for(int j=1;j<=m;j++)
            {
                sf("%d",&v);
                tree[u].push_back(v);
                vis[v]=1;
            }
        }
        int ans;
        for(int i=1;i<=n;i++)
        {
            if(!vis[i])
            {
                DFS(i);
                //cout<<i<<endl;
                ans=min(dp[i][1],dp[i][2]);
                break;
            }
        }
        cout<<ans<<endl;
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值