[BZOJ3993][SDOI2015]星际战争(二分答案+最大流)

先二分时间,记当前判断的时间为 time
建立源点和汇点,建图为:
1、 1iM ,由源点向第 i 个激光武器建一条容量为timeBi的边。
2、 1iN ,由第 i 个巨型机器人向汇点建一条容量为Ai的边。
3、 1iM,1jN ,如果第 i 个激光武器可以攻击第j个机器人,那么由第 i 个激光武器向第j个机器人连一条容量为 的边。
跑一遍最大流,如果最大流等于 Ni=1Ai ,那么在 time 或比 time 更短的时间内,可以摧毁所有的机器人。
注意精度。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const double eps = 1e-9, INF = 1e12;
const int M = 55, N = 2e5 + 5;
int n, m, a[M], b[M], ecnt = 1, nxt[N], adj[N], go[N], lev[N],
len, que[N], cur[N], S, T, g[M][M], sm; double cap[N], Cap[N];
void add_edge(int u, int v, double w) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt;
    go[ecnt] = v; cap[ecnt] = w;
    nxt[++ecnt] = adj[v]; adj[v] = ecnt;
    go[ecnt] = u; cap[ecnt] = 0;
}
bool bfs() {
    int i; for (i = S; i <= T; i++) lev[i] = -1, cur[i] = adj[i];
    que[len = 1] = S; lev[S] = 0;
    for (i = 1; i <= len; i++) {
        int u = que[i];
        for (int e = adj[u], v; e; e = nxt[e])
            if (cap[e] > eps && lev[v = go[e]] == -1) {
                que[++len] = v; lev[v] = lev[u] + 1;
                if (v == T) return 1;
            }
    }
    return 0;
}
double dinic(int u, double flow) {
    if (u == T) return flow;
    double res = 0, d = 0;
    for (int &e = cur[u], v; e; e = nxt[e])
        if (cap[e] > eps && lev[u] < lev[v = go[e]]) {
            d = dinic(v, min(cap[e], flow - res));
            if (d > eps) {
                cap[e] -= d; cap[e ^ 1] += d;
                res += d; if (fabs(flow - res) <= eps)
                    break;
            }
        }
    if (fabs(flow - res) > eps) lev[u] = -1;
    return res;
}
double solve() {
    double ans = 0;
    while (bfs()) ans += dinic(S, INF);
    return ans;
}
void build(double tm) {
    int i; for (i = 2; i <= ecnt; i++)
        cap[i] = Cap[i];
    for (i = 2; i <= (m << 1) + 1; i += 2)
        cap[i] = tm * b[i >> 1];
}
double SOLVE() {
    double l = 0, r = 1e9, mid = (l + r) / 2;
    while (r - l >= eps) {
        build(mid);
        if (1.0 * sm - solve() < eps) r = mid;
        else l = mid;
        mid = (l + r) / 2;
    }
    return mid;
}
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
int main() {
    int i, j; n = read(); m = read();
    for (i = 1; i <= n; i++) a[i] = read(), sm += a[i];
    for (i = 1; i <= m; i++) b[i] = read();
    for (i = 1; i <= m; i++) for (j = 1; j <= n; j++)
        g[i][j] = read(); S = 1; T = n + m + 2;
    for (i = 2; i <= m + 1; i++) add_edge(S, i, 0);
    for (i = m + 2; i < T; i++) add_edge(i, T, 1.0 * a[i - m - 1]);
    for (i = 2; i <= m + 1; i++) for (j = m + 2; j < T; j++)
        if (g[i - 1][j - m - 1]) add_edge(i, j, INF);
    for (i = 2; i <= ecnt; i++) Cap[i] = cap[i];
    printf("%.6lf\n", SOLVE());
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值