如何优雅的用反悔贪心思路做出模拟费用流(HZTG3003. 皮胚 题解)
待补
『抢先看』的意思是还没来得及找歌和图,等找了会重新发。
我不会费用流。
考虑贪心做这道题,首先显然的贪心是每次选最大的没匹配的边,也显然假。
考虑假的原因,发现其事实上可以通过交错路来匹配两个不相关的点,考虑将这样的点对之间连边当做是反悔策略,贡献就是和原来的差。
考虑到每个节点的度最多是 \(20\),正确性和复杂度都是有保证的,但是因为交错路是隔一条边选一条边,很难真正的维护出交错路形成的连通块的形状和叶子(度是一的点),也就很难做到直接将所有决策统计起来。
发现关键性质,\(k \le 200\),这给了我们暴力的机会,考虑每次枚举一条边,我们并不维护所有决策,我们只维护出从某个点出发跳交错路的最大贡献,这个暴力 dfs 跳交错路是容易的。
因为每次最多多选 \(1\) 条边,所以连通块大小也是 \(\mathcal{O}(k)\) 的,单次的复杂度就是 \(k ^ 2\),实现的时候可以先建出整个的图,将边黑白染色,然后暴力交替跳黑白色即可。
现在的复杂度是 \(2 ^ n n k ^ 2\),发现边数很多,但是只有 \(\mathcal{O}(n k)\) 条和交错路连通块相邻的边需要跑交错路,于是这部分我们暴力枚举交错路连通块里的点和与其相连的边来统计,其他边就依然放到堆里跑贪心。
注意到我们的交错路只和与交错路连通块相交的那个点有关,所以枚举边并不在复杂度上限。
发现这些放到堆里的边权值都不变,于是直接基排后用队列维护就可以了,复杂度 \(\mathcal{O}(2 ^ n n + k ^ 3 n)\),足以通过。
跳交错路相当于是退流,所以写出来和模拟费用流一模一样啦~
Code
#include <bits/stdc++.h>
using namespace std;
using llt = long long;
using ull = unsigned long long;
using llf = long double;
#define endl '\n'
#ifdef LOCAL
FILE *InFile = freopen("in_out/in.in", "r", stdin), *OutFile = freopen("in_out/out.out", "w", stdout);
#else
FILE *InFile = freopen("pp.in", "r", stdin), *OutFile = freopen("pp.out", "w", stdout);
#endifconst int LG = 20, N = (1 << LG) + 3;
int n, ck, clg, cv[N];
pair<int, int> ce[N * 10], *ft = ce + 1, *bk = ce + 1;struct Gph{int hd[N], nt[N * 20], to[N * 20], tot = 1; bool wt[N * 20];void Add(int u, int v, bool w){wt[++tot] = w, to[tot] = v, nt[tot] = hd[u], hd[u] = tot;}void ADD(int u, int v, int w){Add(u, v, w), Add(v, u, w);}
#define For_to(i, u, v, g) for(int i = g.hd[u], v = g.to[i]; i; i = g.nt[i], v = g.to[i])
} g;vector<int> nd;
bool vnd[N]; int clv, to[N], vis[N];
int aaa;
int Dfs(int u, bool l){++aaa;vis[u] = clv;if(!vnd[u]) return cv[u];int ma = -0x3f3f3f3f;For_to(i, u, v, g) if(vis[v] != clv && g.wt[i] != l)ma = max(ma, Dfs(v, l ^ 1));return ma;
}
int To(int u, bool l){vis[u] = clv;if(!vnd[u]) return cv[u];int ma = -0x3f3f3f3f;For_to(i, u, v, g) if(vis[v] != clv && g.wt[i] != l){int t = To(v, l ^ 1);if(ma < t)ma = t, to[u] = v;}return ma;
}
void Rev(int u, bool l){if(!vnd[u]){nd.emplace_back(u), vnd[u] = 1;return ;}For_to(i, u, v, g) if(v == to[u])Rev(v, l ^ 1), g.wt[i] ^= 1, g.wt[i ^ 1] ^= 1;
};int main(){ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);cin >> clg >> ck; n = 1 << clg;for(int i = 0; i < n; ++i)cin >> cv[i];{static pair<int, int> tp[N * 10]; int ct = 0;for(int u = 0; u < n; ++u)for(int j = 0; j < clg; ++j) if(u & (1 << j)){int v = u ^ (1 << j);g.ADD(u, v, 0);tp[++ct] = {u, v};}const int V = 2e6 + 3;static int tmp[V];for(int i = 1; i <= ct; ++i){auto &[u, v] = tp[i];++tmp[cv[u] + cv[v]];}for(int i = V - 3; ~i; --i)tmp[i] += tmp[i + 1];for(int i = ct; i; --i){auto &[u, v] = tp[i];ce[tmp[cv[u] + cv[v]]--] = tp[i];}bk += ct;}int ans = 0;memset(to, -1, sizeof to);for(int ccc = 1; ccc <= ck; ++ccc){while(ft != bk){auto [u, v] = *ft; if(vnd[u] || vnd[v]) ++ft;else break;}auto [eu, ev] = *ft;int ema = -1;if(ft != bk)ema = cv[eu] + cv[ev];int nma = 0, nu = 0, nv = 0;for(auto u : nd){int tp = Dfs(u, 0);For_to(i, u, v, g) if(!g.wt[i]){int t = tp + cv[v] * (!vnd[v]);if(t > nma)nma = t, nu = u, nv = v;++clv;}}if(max(nma, ema) <= 0) break;if(nma > ema){ans += nma;To(nu, 0), ++clv;Rev(nu, 0), ++clv;For_to(i, nu, v, g) if(v == nv)g.wt[i] = g.wt[i ^ 1] = 1;if(!vnd[nu])nd.emplace_back(nu), vnd[nu] = 1;if(!vnd[nv])nd.emplace_back(nv), vnd[nv] = 1;}else{ans += ema;++ft;For_to(i, eu, v, g) if(v == ev)g.wt[i] = g.wt[i ^ 1] = 1;nd.emplace_back(eu), nd.emplace_back(ev), vnd[eu] = vnd[ev] = 1;}}cout << ans << endl;
}
待补