bzoj1565 [NOI2009]植物大战僵尸

(http://www.elijahqi.win/2018/01/03/bzoj1565-noi2009%E6%A4%8D%E7%89%A9%E5%A4%A7%E6%88%98%E5%83%B5%E5%B0%B8/)
题目描述

Plants vs. Zombies(PVZ)是最近十分风靡的一款小游戏。Plants(植物)和Zombies(僵尸)是游戏的主角,其中Plants防守,而Zombies进攻。该款游戏包含多种不同的挑战系列,比如Protect Your Brain、Bowling等等。其中最为经典的,莫过于玩家通过控制Plants来防守Zombies的进攻,或者相反地由玩家通过控制Zombies对Plants发起进攻。

现在,我们将要考虑的问题是游戏中Zombies对Plants的进攻,请注意,本题中规则与实际游戏有所不同。游戏中有两种角色,Plants和Zombies,每个Plant有一个攻击位置集合,它可以对这些位置进行保护;而Zombie进攻植物的方式是走到植物所在的位置上并将其吃掉。

游戏的地图可以抽象为一个N行M列的矩阵,行从上到下用0到N–1编号,列从左到右用0到M–1编号;在地图的每个位置上都放有一个Plant,为简单起见,我们把位于第r行第c列的植物记为Pr, c。

Plants分很多种,有攻击类、防守类和经济类等等。为了简单的描述每个Plant,定义Score和Attack如下:

Score[Pr, c]

Zombie击溃植物Pr, c可获得的能源。若Score[Pr, c]为非负整数,则表示击溃植物Pr, c可获得能源Score[Pr, c],若为负数表示击溃Pr, c需要付出能源 -Score[Pr, c]。

Attack[Pr, c]

植物Pr, c能够对Zombie进行攻击的位置集合。

Zombies必须从地图的右侧进入,且只能沿着水平方向进行移动。Zombies攻击植物的唯一方式就是走到该植物所在的位置并将植物吃掉。因此Zombies的进攻总是从地图的右侧开始。也就是说,对于第r行的进攻,Zombies必须首先攻击Pr, M-1;若需要对Pr, c(0≤c< M-1)攻击,必须将Pr,M-1, Pr, M-2 … Pr, c+1先击溃,并移动到位置(r, c)才可进行攻击。

在本题的设定中,Plants的攻击力是无穷大的,一旦Zombie进入某个Plant的攻击位置,该Zombie会被瞬间消灭,而该Zombie没有时间进行任何攻击操作。因此,即便Zombie进入了一个Plant所在的位置,但该位置属于其他植物的攻击位置集合,则Zombie会被瞬间消灭而所在位置的植物则安然无恙(在我们的设定中,Plant的攻击位置不包含自身所在位置,否则你就不可能击溃它了)。

Zombies的目标是对Plants的阵地发起进攻并获得最大的能源收入。每一次,你可以选择一个可进攻的植物进行攻击。本题的目标为,制定一套Zombies的进攻方案,选择进攻哪些植物以及进攻的顺序,从而获得最大的能源收入。
输入输出格式

输入格式:

输入文件pvz.in的第一行包含两个整数N, M,分别表示地图的行数和列数。

接下来N×M行描述每个位置上植物的信息。第r×M + c + 1行按照如下格式给出植物Pr, c的信息:第一个整数为Score[Pr, c], 第二个整数为集合Attack[Pr, c]中的位置个数w,接下来w个位置信息(r’, c’),表示Pr, c可以攻击位置第r’ 行第c’ 列。

输出格式:

输出文件pvz.out仅包含一个整数,表示可以获得的最大能源收入。注意,你也可以选择不进行任何攻击,这样能源收入为0。
输入输出样例
输入样例#1: 复制

3 2
10 0
20 0
-10 0
-5 1 0 0
100 1 2 1
100 0

输出样例#1: 复制

25

说明

约20%的数据满足1 ≤ N, M ≤ 5;

约40%的数据满足1 ≤ N, M ≤ 10;

约100%的数据满足1 ≤ N ≤ 20,1 ≤ M ≤ 30,-10000 ≤ Score ≤ 10000

题意:给出每个植物能够控制的地方 并且给出打掉每个植物获得的分值 每次都只能走一个横行 然后呢必须从最右的第一列开始走 求问 最后干掉这些植物 获得的最大 分值那么首先可以发现 每个植物控制的区域有一个特点 我们可以从每个点向他可以控制的地方连接 然后我可以发现他们之间是存在关系的即 拓扑关系 因为吃掉一个 另外的一些可能能被吃掉了 并且 我还要求 我必须先走右边的点 然后再吃左边的点 那么我的右边那么显然我左右也有个关系我从右连向左 然后拓扑一下把所有的环都去掉 我本来想在拓扑的时候顺带把网络流的边也加上 但因为 这完全不科学啊 wa了好几发

我应该出了拓扑之后再循环一下 把所有不在环上的点都反向建出来 权值inf 因为这就是一个最大权闭合子图啊如果一个点权值是正的就从源向他建权值的边 否则从他向汇点建权值为负的边

跑一下最大流 用所有正的一减即可


#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 0x3f3f3f3f
#define N 660
using namespace std;
inline char gc(){
    static char now[1<<16],*S,*T;
    if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=gc();}
    while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=gc();
    return x*f;
}
struct node{
    int x,y,z,next;
}data[N*N*3],data1[N*N*3];
int num=0,T,id[33][33],level[N],h[N],n,m,h1[N],w[N],in[N],flag[N];
inline void insert1(int x,int y,int z){
    data[++num].y=y;data[num].z=z;data[num].next=h[x];h[x]=num;data[num].x=x;
    data[++num].y=x;data[num].z=0;data[num].next=h[y];h[y]=num;data[num].x=y;
}
inline void insert2(int x,int y){
    data1[++num].y=y;data1[num].next=h1[x];h1[x]=num;data1[num].x=x;
}
inline bool bfs(){
    memset(level,0,sizeof(level));level[0]=1;queue<int>q;q.push(0);
    while(!q.empty()){
        int x=q.front();q.pop();
        for (int i=h[x];i;i=data[i].next){
            int y=data[i].y,z=data[i].z;
            if (level[y]||!z) continue;level[y]=level[x]+1;q.push(y);if (y==T) return 1;
        }
    }return 0;
}
inline int dfs(int x,int s){
    if (x==T) return s;int ss=s;
    for (int i=h[x];i;i=data[i].next){
        int y=data[i].y,z=data[i].z;
        if (level[x]+1==level[y]&&z){
            int xx=dfs(y,min(s,z));if (!xx) level[y]=0;
            s-=xx;data[i].z-=xx;data[i^1].z+=xx;if (!s) return ss;
        }
    }return ss-s;
}
int main(){
//  freopen("2805.in","r",stdin);
    n=read();m=read();int tot=0;queue<int>q;int sum=0;
    for (int i=0;i<n;++i)
        for (int j=0;j<m;++j) id[i][j]=++tot;T=tot+1;
    for (int i=0;i<n;++i){
        for (int j=1;j<m;++j) insert2(id[i][j],id[i][j-1]),++in[id[i][j-1]];
        for (int j=0;j<m;++j){
            w[id[i][j]]=read();int nm=read();
            for (int k=1;k<=nm;++k){
                int x=read(),y=read();insert2(id[i][j],id[x][y]);++in[id[x][y]];
            }
        }
    }
    int num1=num;num=1;for (int i=1;i<=tot;++i) if (!in[i])q.push(i);
    while(!q.empty()){
        int x=q.front();q.pop();flag[x]=1;
        if (w[x]>0) {
            sum+=w[x];insert1(0,x,w[x]);
        }else insert1(x,T,-w[x]);
        for (int i=h1[x];i;i=data1[i].next){
            int y=data1[i].y;if (--in[y]==0) q.push(y);
        }
    }int ans=0;
    for (int i=1;i<=num1;++i){
        int x=data1[i].x,y=data1[i].y;if (!flag[x]||!flag[y]) continue;
        insert1(y,x,inf);
    }
    //for (int i=1;i<=tot;++i) if (w[i]>0) else insert1(i,T,-w[i]);
    //for (int i=2;i<=num;++i) printf("%d %d %d\n",data[i].x,data[i].y,data[i].z);
    while(bfs()) ans+=dfs(0,inf);printf("%d\n",sum-ans);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值