【NOI2013模拟】秘密任务

15 篇文章 0 订阅
5 篇文章 0 订阅

Description:

这里写图片描述
对于 10 0% 的数据: 2 ≤ N ≤ 400, 1 ≤ M ≤ 4 00 0,1 ≤ T ≤ 5,1 ≤ Ai, c ≤ 10^9。无向图可能 有重边 。

题解:

首先建出最短路图,显然第二问就是跑个最小割吗?

第一问相当于问是否有大于一个最小割。

先跑一遍最大流,残量网络就分为三个部分:
1.超级源S能够走到的点。
2.能够走到超级汇T的点。
3.不属于以上两种的。

不显然结论:
若有条边(u,v),u、v分属于1、2部分,则(u,v)一定是任意一组最小割的割边。

这个证明非常简单。

假设(u,v)不能删,即给它的流量赋个inf。

那么现在S和T连通了。

我们必须要找到一条S->u的增广路或是v->T的增广路。

这个流量是不可能小于当前(u,v)的流量的,不然的话,一开始割的就是不是(u,v)了。

至此得证。

注意这题的坑点在于要拆点建图。

因为若有a[u]=a[v],且(u,v)是割集的一条边,其实是有两种割法的。

拆点就可以判掉了。

Code:

#include<cstdio>
#include<cstring>
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;

const int N = 100005;

int Q, n, m, x, y, z;
int final[N], to[N], next[N], w[N], tot;
ll a[N];

void link(int x, int y, int z) {
    next[++ tot] = final[x], to[tot] = y, w[tot] = z, final[x] = tot;
    next[++ tot] = final[y], to[tot] = x, w[tot] = z, final[y] = tot;
}

int d[N * 100], bz[N], bx[N]; ll dis[N], dis2[N];

void spfa(int s, ll *dis) {
    fo(i, 1, n) dis[i] = 1e18;
    dis[s] = 0; d[d[0] = 1] = s; bz[s] = 1;
    fo(i, 1, d[0]) {
        int x = d[i];
        for(int j = final[x]; j; j = next[j]) {
            int y = to[j];
            if(dis[x] + w[j] < dis[y]) {
                dis[y] = dis[x] + w[j];
                if(!bz[y]) d[++ d[0]] = y, bz[y] = 1;
            }
        }
        bz[x] = 0;
    }
}

const int M = 1e6;
int b[405][405];

int S, T, co[M], dt[M], cur[M], tt, ts;

struct edge {
    int final[M], next[M], to[M], tot;
    ll r[M];
    void link(int x, int y, ll z) {
        next[++ tot] = final[x], to[tot] = y, r[tot] = z, final[x] = tot;
        next[++ tot] = final[y], to[tot] = x, r[tot] = 0, final[y] = tot;
    }
    void cl() {
        fo(i, 1, tt) final[i] = dt[i] = co[i] = cur[i] = 0;
        fo(i, 2, tot) next[i] = 0;
        tot = 1;
    }
} e;

ll dfs(int x, ll flow) {
    if(x == T) return flow;
    ll use = 0;
    for(int i = cur[x]; i; i = e.next[i], cur[x] = i) {
        int y = e.to[i];
        if(e.r[i] && dt[y] + 1 == dt[x]) {
            ll tmp = dfs(y, min(flow - use, e.r[i]));
            e.r[i] -= tmp, e.r[i ^ 1] += tmp, use += tmp;
            if(use == flow) return use;
        }
    }
    cur[x] = e.final[x];
    if(!(-- co[dt[x]])) dt[S] = tt;
    ++ co[++ dt[x]];
    return use;
}

void dg(int x) {
    if(bz[x]) return;
    bz[x] = 1;
    for(int i = e.final[x]; i; i = e.next[i])
        if(e.r[i]) dg(e.to[i]);
}

void dg2(int x) {
    if(bz[x]) return;
    bz[x] = 2;
    for(int i = e.final[x]; i; i = e.next[i])
        if(e.r[i ^ 1]) dg2(e.to[i]);
}


int main() {
    for(scanf("%d", &Q); Q; Q --) {
        fo(i, 1, tot) next[i] = 0;
        fo(i, 1, n) final[i] = 0;
        tot = 0;
        scanf("%d %d", &n, &m);
        fo(i, 1, n - 1) scanf("%lld", &a[i]);
        a[n] = 1e18;
        fo(i, 1, m) {
            scanf("%d %d %d", &x, &y, &z);
            link(x, y, z);
        }
        fo(i, 1, n) bz[i] = 0;
        spfa(1, dis); spfa(n, dis2);
        fo(i, 1, n) bx[i] = (dis[i] + dis2[i] == dis[n]);
        fo(i, 1, n) fo(j, 1, n) b[i][j] = 0;
        fo(x, 1, n) if(bx[x]) for(int j = final[x]; j; j = next[j])
            if(dis[x] + w[j] == dis[to[j]] && bx[to[j]])
                b[x][to[j]] ++;
        e.cl();
        S = 1; T = n; tt = T;
        fo(i, 1, n) fo(j, 1, n) if(b[i][j]) {
            tt ++;
            e.link(i, tt, (ll) b[i][j] * a[i]),
            e.link(tt, j, (ll) b[i][j] * a[j]);
        }
        co[0] = tt; ll ans = 0;
        for(; dt[S] < tt;) ans += dfs(S, 1LL << 62);
        fo(i, 1, tt) bz[i] = 0;
        dg(S); dg2(T);
        ll ans2 = 0;
        fo(i, 1, tt) for(int j = e.final[i]; j; j = e.next[j])
            if(bz[i] && bz[e.to[j]] && bz[i] != bz[e.to[j]])
                ans2 += e.r[j];
        if(ans == ans2) printf("Yes "); else printf("No ");
        printf("%lld\n", ans);
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值