[51nod1299]监狱逃离 树形DP || 20w个点的网络流最小割ORZ

监狱有N条道路连接N + 1个交点,编号0至N,整个监狱被这些道路连在一起(任何2点之间都有道路),人们通过道路在交点之间走来走去。其中的一些交点只有一条路连接,这些点是监狱的出口。在各个交点中有M个点住着犯人(M <= N + 1),剩下的点可以安排警卫,有警卫把守的地方犯人无法通过。给出整个监狱的道路情况,以及犯人所在的位置,问至少需要安排多少个警卫,才能保证没有1个犯人能够逃到出口,如果总有犯人能够逃出去,输出-1。

如上图所示,点1,6 住着犯人,0,4,5,7,8是出口,至少需要安排4个警卫。
Input

第1行:2个数N, M中间用空格分隔,N表示道路的数量,M表示犯人的数量(1<= N <= 100000, 0 <= M <= N + 1)。
之后N行:每行2个数S, E中间用空格分隔,表示点编号为S的点同编号为E的点之间有道路相连。(0 <= S, E <= N)。
之后的M行,每行1个数Pi,表示编号为Pi的点上有犯人。

Output

输出1个数对应最少需要多少警卫才能不让犯人逃出监狱。

Input示例

8 2
0 1
1 2
2 3
3 4
3 5
2 6
6 8
6 7
1
6

Output示例

4

ORZ ,学长在网络流专题里放这么一道毒瘤题,20w个点,他说可以用网络流遛一遛。。。。 我自己写的网络流交了十多发才卡过去。。。。
ヽ(ー_ー)ノ

正解应该是树形DP,那时候并不了解树形DP。。 看题解都理解了好久。。。。。
这道题跟UVA1218的状态转移方程有一些相似。有兴趣的可以去看一哈。 也挺有意思的
**

解题思路

**

他给出的是一棵树,求的是最小花费问题,图上的最小花费一般是最短路,网络流,或者DP问题了。 这里由于20w个点,网络流应该首先被pass掉(实在不行也可以试一发,暴力出奇迹)。
这题看不出跟最短路有啥关系,我们应该往树形DP的方向去想。
我们来考虑一下,放完警卫后,这个树形图上每个点的状态会是怎么样的。
状态一. 这个点的子树上的逃犯逃不到这个点,这个点也无法通向的它子树的叶子节点
状态二. 这个点的子树上的逃犯逃不到这个点,这个点有通向其叶子节点的路径
状态三. 这个点的子树上的逃犯能到达这个节点,这个节点无法通向其子树的叶子节点。
除此之外,如果这个图拥有其他状态的点,那么绝对不合法。
这里在我们更新子节点的时候不用考虑这个节点父亲节点的状况,因为父亲节点的状态就是由子节点唯一确定的。
状态很少,所以我们可以很轻易的枚举所有状态。 由此可以写出dp数组的定义

int dp[100005];
// 0表示子节点的逃犯无法到达这个节点,该节点可通向叶子结点。 既叶子节点的初始状态
// 1表示 表示子节点逃犯无法到达这个节点,该节点不可通向叶子结点
// 2表示该节点的子树的有逃犯可以到达该节点,且该节点不可以通向叶子节点 囚犯所在的节点都会是这个状态

怎么转移呢。 分类讨论一下,判断出每种状态的优先级不停if else即可。。。。。。。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<limits>
#include<map>
#include<set>
#include<stack>
#include<string>
using namespace std;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define fuck(x) cout<<"["<<x<<"]"<<endl
#define mem(a,b) memset(a,b,sizeof a);
class edges
{
public:
    int u,v,next;
};
edges edge[100005*2];
int tot=0;
int head[100005];
int ans=0;
void init()
{
    mem(head,-1);
    tot=0;
    ans=0;
}
void add(int u,int v)
{
    edge[tot].u=u; edge[tot].v=v;
    edge[tot].next=head[u];
    head[u]=tot; tot++;
}
int people[100005];
int dp[100005];
// 0表示子节点的逃犯无法到达这个节点,该节点可通向叶子结点。 既叶子节点的初始状态
// 1表示 表示子节点逃犯无法到达这个节点,该节点不可通向叶子结点
// 2表示该节点的子树的有逃犯可以到达该节点,且该节点不可以通向叶子节点 囚犯所在的节点都会是这个状态
void dfs(int root,int per)
{
    int s[3]={0,0,0};
    for(int i=head[root];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(v==per)
            continue;
        dfs(v,root);
        s[dp[v]]++;
    }
    if(people[root])
    {
        dp[root]=2;
        ans+=s[0];
    }
    else if(s[2]&&s[0])
    {
        ans++;
        dp[root]=1;
    }
    else if(s[0])
    {
        dp[root]=0;
    }
    else if(s[2])
    {
        dp[root]=2;
    }
    else if(s[1])
    {
        dp[root]=1;
    }
}
int du[100005];
int main()
{
    int n,m;
    scanf("%d %d",&n,&m);
    init();
    for(int i=1;i<=n;i++)
    {
        int u,v;
        scanf("%d %d",&u,&v);
        add(u,v);
        add(v,u);
        du[u]++;du[v]++;
    }
    for(int i=1;i<=m;i++)
    {
        int root;
        scanf("%d",&root);
        people[root]=1;
    }
    for(int i=1;i<n;i++)
    {
        if(du[i]==1)
        {
            dfs(i,-1);
            if(dp[i]==2)
                ans++;
            cout<<ans<<endl;
            break;
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值