「2020-2021 集训队作业」Storm

传送门

(这题直接咕了一个月,因为刚开就羊了,然后后面又去忙期末考了;本来当康复训练了,结果因为一个煞笔错误看了一天)


思路

首先我们要确定几条性质:

  1. 当有一条由 \(3\) 条边连成的链时,我们不选中间那条一定更优;

  2. 当我们选出一个环时,我们可以去掉环上任意一条边,答案一定更优。

也就是说,我们选出的边应该构成若干个不相交的直径为 \(2\) 的树,也就是若干个菊花图。

显然,我们可以对菊花图进行黑白染色,得到一个二分图。

假设我们现在已经弄出一个二分图,我们考虑怎么求出答案。

  1. 对于白点 \(i\),我们由源点 \(S\) 向它连出一条流量为 \(1\),费用为 \(-a_i\)的边;再连一条流量无限,费用为 \(0\) 的边;

  2. 对于黑点 \(i\),我们由它向汇点 \(T\) 连出一条流量为 \(1\),费用为 \(-a_i\)的边;再连一条流量无限,费用为 \(0\) 的边;

因为每个点的贡献只算一次,所以要分开连。

  1. 对于一条原图中的边 \(u,v,w\),若 \(u\) 为白色,\(v\) 为黑色,那么由 \(u\)\(v\) 连出一条流量为 \(1\),贡献为 \(w\)

这样我们就直接跑费用流,最小代价取反就是答案。

复杂度是 \(O(k^2(n+m))\),证明可以看官方题解

但对于原图只能自己染色,很可能无法取到最佳答案。

实际上我们只需要染 \(2^kc\) 次,\(c\) 是一个较小的常数。

证明:

  • 对于一个 \(n\) 个点的菊花图,它被染对颜色有 \(2\) 种方案,因此染对的概率为 \(\frac{2}{2^n}=2^{1-n}\)

  • 如果答案的菊花图点数分别是 \(n_1,n_2,...,n_t\),那么一定有 \((n_1-1)+(n_2-1)+...+(n_t-1)\le k\)(因为所选的边数不超 \(k\))。

  • 因此,每个菊花图都被染对色的概率为 \(2^{(1-n_1)}2^{(1-n_2)}...2^{(1-n_t)}\ge 2^{-k}\)


代码

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define max(x...) max({x})
#define min(x...) min({x})
#define FOR(i, x, y) for(int i = (x); i <= (y); i++)
#define ROF(i, x, y) for(int i = (x); i >= (y); i--)
inline int rd()
{
    int sign = 1, re = 0; char c = getchar();
    while(!isdigit(c)){if(c == '-') sign = -1; c = getchar();}
    while(isdigit(c)){re = re * 10 + (c - '0'); c = getchar();}
    return sign * re;
}
const int N = 1000005;
int n, m, k, S, T, used, ans;
int a[N], x[N], y[N], b[N], ty[N];
struct Node
{   
    int to, nxt, fl, w;
}e[N << 1]; int he[N];
int ecnt = 1;
inline void Edge_add(int u, int v, int fl, int w)
{
    e[++ecnt] = (Node){v, he[u], fl, w};
    he[u] = ecnt;
}
mt19937 rng(time(0));
int dis[N], la[N];
deque<int> q;
bitset<N> vis;
inline void SPFA()
{
    FOR(i, 1, T) dis[i] = 2e9 + 1,  la[i] = 0;
    dis[S] = 0, q.push_back(S), vis[S] = 1;
    while(!q.empty())
    {
        int now = q.front(); q.pop_front(), vis[now] = 0;
        for(int i = he[now]; i; i = e[i].nxt)
        {
            if(!e[i].fl) continue;
            int to = e[i].to;
            if(dis[to] > 1ll * dis[now] + e[i].w)
            {
                dis[to] = dis[now] + e[i].w;
                la[to] = i;
                if(!vis[to])
                {
                    vis[to] = 1;
                    if(!q.empty() && dis[q.front()] > dis[to]) q.push_front(to);
                    else q.push_back(to);
                    if(dis[q.front()] > dis[q.back()]) swap(q.front(), q.back());
                }
            }
        }
    }
}
inline void MinCostMaxFlow()
{
    int re = 0; used = k;
    while(1)
    {
        SPFA();
        if(dis[T] >= 0) break;
        used--, re += dis[T];
        if(!used) break;
        int now = T;
        while(now != S)
        {
            e[la[now]].fl--, e[la[now] ^ 1].fl++;
            now = e[la[now] ^ 1].to;
        }
    }
    ans = max(ans, -re);
}
inline void solve()
{
    ans = 0;
    n = rd(), m = rd(), k = rd(), S = n + 1, T = n + 2;
    FOR(i, 1, n) a[i] = rd();
    FOR(i, 1, m) x[i] = rd(), y[i] = rd(), b[i] = rd();
    FOR(i, 1, (1 << k) * 5)
    {
        FOR(j, 1, n) ty[j] = rng() & 1u;
        ecnt = 1;
        FOR(j, 1, T) he[j] = 0;
        FOR(j, 1, n)
            if(!ty[j])
                Edge_add(S, j, 1, -a[j]), Edge_add(j, S, 0, a[j]),
                Edge_add(S, j, N, 0), Edge_add(j, S, 0, 0);
            else
                Edge_add(j, T, 1, -a[j]), Edge_add(T, j, 0, a[j]),
                Edge_add(j, T, N, 0), Edge_add(T, j, 0, 0);
        FOR(j, 1, m) if(ty[x[j]] != ty[y[j]])
        {
            if(ty[y[j]]) Edge_add(x[j], y[j], 1, b[j]), Edge_add(y[j], x[j], 0, -b[j]);
            else Edge_add(y[j], x[j], 1, b[j]), Edge_add(x[j], y[j], 0, -b[j]);
        }
        MinCostMaxFlow();
    }
    printf("%d\n", ans);
}
signed main()
{
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    int T = rd();
    while(T--) solve();
    return 0;
}
posted @ 2023-01-13 22:19  zuytong  阅读(45)  评论(0编辑  收藏  举报