[bzoj1565][NOI2009] PVZ 最大权闭合图

本文解析了NOI2009植物大战僵尸题目,通过拓扑排序和最大权闭合子图算法解决游戏策略问题,实现获取最大能源收益。

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

1565: [NOI2009]植物大战僵尸

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1565
Time Limit: 10 Sec Memory Limit: 64 MB
Submit: 2508 Solved: 1147
[Submit][Status][Discuss]

Description

这里写图片描述

Input

这里写图片描述
这里写图片描述

小学回忆

Output

仅包含一个整数,表示可以获得的最大能源收入。注意,你也可以选择不进行任何攻击,这样能源收入为0。
Sample Input

3 2

10 0

20 0

-10 0

-5 1 0 0

100 1 2 1

100 0

Sample Output

25

HINT

在样例中, 植物P1,1可以攻击位置(0,0), P2, 0可以攻击位置(2,1)。
一个方案为,首先进攻P1,1, P0,1,此时可以攻击P0,0 。共得到能源收益为(-5)+20+10 = 25。注意, 位置(2,1)被植物P2,0保护,所以无法攻击第2行中的任何植物。
【大致数据规模】
约20%的数据满足1 ≤ N, M ≤ 5;
约40%的数据满足1 ≤ N, M ≤ 10;
约100%的数据满足1 ≤ N ≤ 20,1 ≤ M ≤ 30,-10000 ≤ Score ≤ 10000 。

注:样例表示的数据并非按行列输入。

我们发现,一株植物必须被吃才能干掉被他所保护的植物和他左边的植物,因此,我们很容易会想到拓扑序,而有环的植物及其保护的植物事无论如何都无法吃掉的,删去这些点,就是一道最大权闭合子图的裸题了。

这道题是我刚学“Hellow World!”就在网上看到的题
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define pa pair<int,int>
#define INF 1000000000
#define ll long long 
#define p(x,y) (x-1)*m+y
using namespace std;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,m,T;
int tot,top,cnt,ans;
int v[605],cur[605],h[605],in[605],q[605];
bool del[605];
struct edge{
    int to,next,v;
}e[400005],ed[800005];int head[605],hed[605];
void adde( int u, int v ){
    in[v]++;
    e[++cnt].to=v;e[cnt].next=head[u];head[u]=cnt;
}
void adde2( int u, int v, int w ){
    ed[++cnt].to=v; ed[cnt].next=hed[u]; hed[u]=cnt; ed[cnt].v=w;
    ed[++cnt].to=u; ed[cnt].next=hed[v] ;hed[v]=cnt; ed[cnt].v=0;
}
void dfs( int x ){
    del[x] = 1;
    for( int i = head[x]; i; i = e[i].next ) 
        if( !del[e[i].to] ) dfs(e[i].to);//有环植物保护的任何一株植物都不能打 
}
void topsort() {
    for( int i = 1; i <= m*n; i++ )
        if( !in[i] ) q[++top] = i;
        else del[i] = 1;
    while( top ) {
        int now = q[top];del[now] = 0;//清除标记 
        top--;
        for( int i = head[now]; i; i = e[i].next) {
            in[e[i].to]--;
            if( !in[e[i].to] ) q[++top] = e[i].to;
        }
    }
    for( int i = 1; i <= n*m; i++ )
        if( del[i] ) dfs( i );//始终有入度,说明有环 
}
void rebuild() {
    cnt = 1;T = n*m+1;
    for( int x = 1; x <= n*m; x++ ) {
        if( !del[x] ) {//由最大权闭合子图转网络流 并用最小割 
            if( v[x] > 0 ) tot += v[x], adde2( x, T, v[x] );
            else adde2( 0, x, -v[x] );
            for( int i = head[x]; i; i = e[i].next ) 
                if( !del[e[i].to] ) adde2( x, e[i].to, INF );
        }
    }
}
bool bfs(){//是否割完 
    int hd = 0;int tail = 1;
    for( int i = 1; i <= T; i++ ) h[i] = -1;
    h[0] = 0;q[0] = 0;
    while( hd != tail ) {
        int now = q[hd];hd++;
        for( int i = hed[now]; i; i = ed[i].next )
            if( ed[i].v && h[ed[i].to] == -1 )
                h[ed[i].to] = h[now]+1,q[tail++] = ed[i].to;
    }
    return h[T] != -1;
}
int dfs( int x, int f ){
    if( x == T ) return f;
    int w, used = 0;
    for( int i = cur[x]; i; i = ed[i].next)
        if( h[ed[i].to] == h[x] + 1 ) {
            w = dfs(ed[i].to,min(ed[i].v,f-used));
            ed[i].v -= w; ed[i^1].v += w;
            if( ed[i].v ) cur[x] = i;
            used += w; if( used == f ) return f;
        }
    if( !used ) h[x] = -1;
    return used;
}
void dinic(){
    while( bfs( ) ) {
        for( int i = 0; i <= T; i++ ) cur[i] = hed[i];
        ans += dfs( 0, INF );
    }
}
int main(){
    n = read();m = read();
    for( int i = 1; i <= n; i++ )
        for( int j = 1; j <= m; j++ ){
            v[p(i,j)] = read();
            int x = read();
            while( x-- ){
                int a = read(), b = read();
                a++; b++;
                adde( p(i,j), p(a,b) ); //保护关系 
            }
        }
    for( int i = 1; i <= n; i++ )
        for( int j = m; j > 1; j-- )
            adde( p(i,j), p(i,j-1) );//相当于右边的植物保护左边的植物 
    topsort();
    rebuild();
    dinic();
    printf( "%d", tot-ans );
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值