JZOJ 5602. 【NOI2018模拟3.26】Cti & JZOJ 5057. 【GDSOI2017模拟4.13】炮塔

9 篇文章 0 订阅
6 篇文章 0 订阅

Description

有一个 n × m 的地图, 地图上的每一个位置可以是空地, 炮塔或是敌人. 你需要操纵炮塔消灭敌人.
对于每个炮塔都有一个它可以瞄准的方向, 你需要在它的瞄准方向上确定一个它的攻击位置,当然也可以不进行攻击. 一旦一个位置被攻击, 则在这个位置上的所有敌人都会被消灭.
保证对于任意一个炮塔, 它所有可能的攻击位置上不存在另外一个炮塔.
定义炮弹的运行轨迹为炮弹的起点和终点覆盖的区域. 你需要求出一种方案, 使得没有两条炮弹轨迹相交.

Input

第一行两个整数 n,m.
接下来 n 行, 每行 m 个整数, 0 表示空地, −1,−2,−3,−4 分别表示瞄准上下左右的炮塔, 正整
数 p 表示表示此位置有 p 个敌人.

Output

一行一个整数表示答案.

Sample Input

输入1:

3 2
0 9
-4 3
0 -1

输入2:

4 5
0 0 -2 0 0
-4 0 5 4 0
0 -4 3 0 6
9 0 0 -1 0

Sample Output

输出1:

9

输出2:

12

Data Constraint

对于前 20% 的数据, n,m ≤ 5;
对于另 20% 的数据, 朝向上下的炮塔至多有 2 个;
对于另 20% 的数据, 至多有 6 个炮塔;
对于 100% 的数据, 1 ≤ n,m ≤ 50, 每个位置的敌人数量 < 1000.

Solution

  • 博主偷懒 COPY 一波题解:(感谢 YaLi_HYJ 大佬的详细解析)

Solution
Solution

  • 这题用网络流解决问题十分巧妙,在解决相交的问题上采取了拆点的方法。

  • 最后跑一遍最小割来求得最小代价即可。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
using namespace std;
const int N=52,M=N*N<<1,inf=1e9;
struct data
{
    int x,y;
}b[N*N];
int n,m,num,tot=1,p,ans,sum,s,t;
int first[M],next[M<<1],en[M<<1],w[M<<1];
int a[N][N],d[N][N],r[M],c[M];
int dis[M],gap[M],cur[M];
inline int read()
{
    int X=0,w=0; char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}
inline int min(int x,int y)
{
    return x<y?x:y;
}
inline void ins(int x,int y,int z)
{
    next[++tot]=first[x];
    first[x]=tot;
    en[tot]=y;
    w[tot]=z;
}
inline void insert(int x,int y,int z)
{
    ins(x,y,z),ins(y,x,0);
}
int sap(int x,int y)
{
    if(x==t) return y;
    int use=0;
    for(int i=cur[x];i;i=next[i])
        if(w[i] && dis[x]==dis[en[i]]+1)
        {
            cur[x]=i;
            int z=sap(en[i],min(w[i],y-use));
            w[i]-=z,w[i^1]+=z,use+=z;
            if(dis[s]>p || use==y) return use;
        }
    cur[x]=first[x];
    if(!--gap[dis[x]]) dis[s]=p+1;
    gap[++dis[x]]++;
    return use;
}
int main()
{
    n=read(),m=read();
    s=++p,t=++p;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            a[i][j]=read();
            if(a[i][j]<0) b[++num]=(data){i,j};
            int x=d[i][j]=(i-1)*m+j;
            r[x]=++p,c[x]=++p;
            insert(c[x],r[x],inf);
        }
    for(int i=1;i<=num;i++)
    {
        int xx=b[i].x,yy=b[i].y,k=a[xx][yy];
        int x=d[xx][yy];
        if(k==-1 || k==-2) insert(s,c[x],inf); else insert(r[x],t,inf);
        if(k==-1)
        {
            int mx=0,pos=0;
            for(int j=xx;j;j--)
                if(a[j][yy]>mx) mx=a[pos=j][yy];
            if(!mx) continue;
            sum+=mx;
            for(int j=xx;j>=pos;j--)
                if(j>1) insert(c[d[j][yy]],c[d[j-1][yy]],mx-(a[j][yy]<0?0:a[j][yy]));
        }else
        if(k==-2)
        {
            int mx=0,pos=0;
            for(int j=xx;j<=n;j++)
                if(a[j][yy]>mx) mx=a[pos=j][yy];
            if(!mx) continue;
            sum+=mx;
            for(int j=xx;j<=pos;j++)
                if(j<n) insert(c[d[j][yy]],c[d[j+1][yy]],mx-(a[j][yy]<0?0:a[j][yy]));
        }else
        if(k==-3)
        {
            int mx=0,pos=0;
            for(int j=yy;j;j--)
                if(a[xx][j]>mx) mx=a[xx][pos=j];
            if(!mx) continue;
            sum+=mx;
            for(int j=yy;j>=pos;j--)
                if(j>1) insert(r[d[xx][j-1]],r[d[xx][j]],mx-(a[xx][j]<0?0:a[xx][j]));
        }else
        {
            int mx=0,pos=0;
            for(int j=yy;j<=m;j++)
                if(a[xx][j]>mx) mx=a[xx][pos=j];
            if(!mx) continue;
            sum+=mx;
            for(int j=yy;j<=pos;j++)
                if(j<m) insert(r[d[xx][j+1]],r[d[xx][j]],mx-(a[xx][j]<0?0:a[xx][j]));
        }
    }
    for(int i=1;i<=p;i++) cur[i]=first[i];
    gap[0]=p;
    while(dis[s]<=p) ans+=sap(s,inf);
    printf("%d",sum-ans);
    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值