uva 12264 (Risk) 二分 + 最大流

题意: 首先有n个点, 然后每个点都一个权值ai表示这个点上的士兵数量,如果ai为0则表示这个点是敌人的, 如果大于零则表示这个点是自己的并且有ai个士兵, 现在你只有一次机会可以移动所有的士兵, 注意 每个士兵只能移动一次, 并且只有两种选择, 要么不动, 要么移动到相邻的一个单位处的的自己的点上, 并且自己的点在移动完之后至少要存在一名士兵。 另外再输入一个n * n的字符矩阵, 如果s【i】【j】 为Y则i, j相邻, 并且 i, i永远为N; 好了, 现在要求出 当敌人的点的相邻自己点上的士兵都达到最大值的时候, 自己的电商的士兵数量最小的点的士兵数量是多少。


思路: 一个很nice的思路, 我也是看到了题解之后才恍然大悟, 首先你要求最大的最小, 第一反应肯定是二分。但是之后又会想 二分怎么分上下呢。。。这就很尴尬了。 我开始看这个题的时候根本没想到网络流。。。。。然后想了一段时间也没想出来就看了题解, 二分是对了, 分上下界的时候要用的网络流, 建图的思想就是把这些点分开, 因为不是自己的点不能放士兵, 所以我们不管这些点, 然后我们把剩下自己的点分成和不是自己的点相邻的点和仅与自己的点相邻的点, 其次我们把每个点分成i和i', 首先i连源点流量为a[i], 然后i和i'连, 流量为a[i], i’是要连汇点的所以这样可以保证连完图之后士兵只走一次, 然后因为是自己的点, 所以每个点都可以给予相邻的自己点的流量, 假如i跟j相邻, i,j都是自己的点, 那么连i到j‘流量为a[i], 这很好理解, i点自己的士兵可以走到j点, 因为士兵只能走一次, 所以i只能给j自己点上的士兵, 之后连汇点就有区别了, 如果这个点和敌人的点相邻, 那么这个i’连汇点, 流量为二分出来的mid, 否则连1, 且mid的最小值为1, 这样应该好理解吧, 我们二分就是要二分最小的那个值, 然后跑最大流看看每次是不是满流, 这样建图之后我们就可以看到每次二分出来的最小值对这个网络有没有影响, 因为这样建图我们最后得出来的最大流是所有士兵的总和, 满流了, 那么就可行, 继续向上二分,反之向下。



代码:这里用的EK, 较慢, 因为忘了带dinic了。。。。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
#define INF 0x3f3f3f3f
const int maxed = 300 + 10;
struct E
{
    int from, to, cap, flow;
};
struct EK
{
    int n, m;
    vector<E> edg;
    vector<int> G[maxed];
    int a[maxed];
    int p[maxed];

    void init(int n)
    {
        for (int i = 1; i <= n; ++i)
            G[i].clear();
        edg.clear();
    }

    void add_(int from, int to, int cap)
    {
        edg.push_back((E){from, to, cap, 0});
        edg.push_back((E){to, from, 0, 0});
        m = edg.size();
        G[from].push_back(m - 2);
        G[to].push_back(m - 1);
    }

    int Maxflow(int s, int t)
    {
        int flow = 0;
        for (;;) {
            memset(a, 0, sizeof(a));
            queue<int> Q;
            Q.push(s);
            a[s] = INF;
            while (!Q.empty()) {
                int x = Q.front(); Q.pop();
                for (int i = 0; i < G[x].size(); ++i) {
                    E& e = edg[G[x][i]];
                    if (!a[e.to] && e.cap > e.flow) {
                        p[e.to] = G[x][i];
                        a[e.to] = min(a[x], e.cap - e.flow);
                        Q.push(e.to);
                    }
                }
                if (a[t])
                    break;
            }
            if (!a[t])
                break;
            for (int u = t; u != s; u = edg[p[u]].from) {
                edg[p[u]].flow += a[t];
                edg[p[u] ^ 1].flow -= a[t];
            }
            flow += a[t];
        }
        return flow;
    }
};
char s[maxed][maxed];
int n, p[maxed];
int main()
{
    bool slove(int mid);
    int N;
    scanf("%d", &N);
    while (N--) {
        scanf("%d", &n);
        int max_ = 0, min_ = 1;
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &p[i]);
            max_ += p[i];
        }
        for (int i = 1; i <= n; ++i)
            scanf("%s", s[i] + 1);
        int answer = 1;
        while (max_ >= min_) {
            int mid = (max_ + min_) >> 1;
            if (slove(mid)) {
                min_ = mid + 1;
                answer = max(answer, mid);
            }
            else
                max_ = mid - 1;
        }
        printf("%d\n", answer);
    }
    return 0;
}
bool slove(int mid)
{
    EK ek;
    int sum = 0;
    for (int i = 1; i <= n; ++i)
        if (p[i]) {
            ek.add_(0, i, p[i]);
            ek.add_(i, i + n, p[i]);
            bool flag = false;
            for (int j = 1; j <= n; ++j)
                if (s[i][j] == 'Y') {
                    if (!p[j])
                        flag = true;
                    else
                        ek.add_(i, j + n, p[i]);
                }
            if (flag) {
                ek.add_(i + n, 2 * n + 1, mid);
                sum += mid;
            }
            else {
                ek.add_(i + n, 2 * n + 1, 1);
                sum += 1;
            }
        }
    return ek.Maxflow(0, 2 * n + 1) == sum ? true : false;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值