bzoj4657 tower [网络流]

本文介绍了一种使用网络流算法解决炮塔攻击问题的方法。该问题要求炮塔在不相交的轨迹中消灭敌人,通过将问题转化为最小割问题并应用网络流算法找到最优解。

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

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


Solution:
吐槽:
看起来就是网络流,但是考试的时候并没有想出来利用最大值的方法,于是放弃了类似切糕的做法,写了一个费用流,结果发现导弹能转弯,于是内心崩溃,第二题也没码出来。

首先转换为能够用最小割解决的问题,那么对于每行每列我们求出最大值,相邻点之间的容量为最大值-权值,这样就能表示出选哪个点。然后考虑如何不能相交,源连向横着射击的点,竖着射击的点连向汇。之后利用了类似切糕的想法,把每个交点拆成横竖两点,之间连inf,这样就保证了在这个交点前肯定会选一个权值,并且只有一个射击塔会选。
感觉最妙的地方还是在于利用最大值来解决这个问题。


#include <bits/stdc++.h>
using namespace std;
const int maxn = 55, maxp = 55 * 55 * 3, inf = 1e9;
struct edge {
    int nxt, to, f;
} e[2000005];
const int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, -1, 1};
#define id(x, y, z) z * n * m + (x - 1) * m + y
int n, m, ans, cnt = 1, source, sink;
int h[maxp], iter[maxp], d[maxp], a[maxn][maxn];
void link(int u, int v, int f) {
    e[++cnt].nxt = h[u];
    h[u] = cnt;
    e[cnt].to = v;
    e[cnt].f = f;
}
void insert(int u, int v, int f) {
    link(u, v, f);
    link(v, u, 0);
}
bool bfs() {
    queue<int> q;
    q.push(source);
    memset(d, -1, sizeof(d));
    d[source] = 0;
    while(!q.empty()) {
        int u = q.front();
        q.pop();
        for(int i = h[u]; i; i = e[i].nxt) {
            if(d[e[i].to] == -1 && e[i].f) {
                d[e[i].to] = d[u] + 1;
                q.push(e[i].to);
            }
        }
    }
    return d[sink] != -1;
}
int dfs(int u, int delta) {
    if(u == sink) {
        return delta;
    }
    int ret = 0;
    for(int &i = iter[u]; i; i = e[i].nxt) {
        if(d[e[i].to] == d[u] + 1 && e[i].f) {
            int x = dfs(e[i].to, min(delta, e[i].f));
            e[i].f -= x;
            e[i ^ 1].f += x;
            delta -= x;
            ret += x;
            if(!delta) {
                break;
            }
        }
    }
    return ret;
} 
int dinic() {
    int ret = 0;
    while(bfs()) {
        for(int i = source; i <= sink; ++i) {
            iter[i] = h[i];
        }
        ret += dfs(source, inf);
    }
    return ret;
}
int main() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= m; ++j) {
            scanf("%d", &a[i][j]);
        }
    }
    sink = n * m * 2 + 1;
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= m; ++j) {
            if(a[i][j] < 0) {
                int mx = 0, x = i, y = j, t = -a[i][j] - 1;
                while(1) {
                    x += dx[t];
                    y += dy[t];
                    if(x < 1 || x > n || y < 1 || y > m) {
                        break;
                    }
                    mx = max(mx, a[x][y]);
                }
                ans += mx;
                if(t < 2) {
                    insert(source, id(i, j, 0), inf);
                } else {
                    insert(id(i, j, 1), sink, inf);
                }
                x = i;
                y = j;
                while(1) {
                    int xx = x, yy = y;
                    x += dx[t];
                    y += dy[t];
                    if(x < 1 || x > n || y < 1 || y > m) {
                        break;
                    }
                    if(t < 2) {
                        insert(id(xx, yy, 0), id(x, y, 0), mx - max(0, a[xx][yy]));
                    } else {
                        insert(id(x, y, 1), id(xx, yy, 1), mx - max(0, a[xx][yy]));
                    }
                }
            } else {
                insert(id(i, j, 0), id(i, j, 1), inf);
            }
        }
    }
    printf("%d\n", ans - dinic());
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值