「2020-2021 集训队作业」Storm
(这题直接咕了一个月,因为刚开就羊了,然后后面又去忙期末考了;本来当康复训练了,结果因为一个煞笔错误看了一天)
思路
首先我们要确定几条性质:
-
当有一条由 \(3\) 条边连成的链时,我们不选中间那条一定更优;
-
当我们选出一个环时,我们可以去掉环上任意一条边,答案一定更优。
也就是说,我们选出的边应该构成若干个不相交的直径为 \(2\) 的树,也就是若干个菊花图。
显然,我们可以对菊花图进行黑白染色,得到一个二分图。
假设我们现在已经弄出一个二分图,我们考虑怎么求出答案。
-
对于白点 \(i\),我们由源点 \(S\) 向它连出一条流量为 \(1\),费用为 \(-a_i\)的边;再连一条流量无限,费用为 \(0\) 的边;
-
对于黑点 \(i\),我们由它向汇点 \(T\) 连出一条流量为 \(1\),费用为 \(-a_i\)的边;再连一条流量无限,费用为 \(0\) 的边;
因为每个点的贡献只算一次,所以要分开连。
- 对于一条原图中的边 \(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;
}