洛谷 P4510 - [CTSC2015]性别改造计划(网络流+状压 dp)
一道很烦的网络流题,代码每一块的结构都很清晰,但是拼起来就让人觉得很烦……
首先此题的一大难点在于读题,我一开始傻傻地以为散点没有用,心想这题不是 sb 状压 dp 吗,后来才发现是自己都错题了……事实上本题正确的题意如下:
- 对于血缘链上的点,任何一条链上的第 \(j\) 个点的深度都是 \(j\)。
- 对于不在血缘链上的点,我们可以为其附上适当的深度使得对于所有“繁衍关系”,都有 \(dep_x=dep_y\)。
如果没有 \(\lfloor 10\ln(1+A)\rfloor\) 这个系数的存在,那么不难发现此题就是一个裸的集合划分模型。具体来说建立源汇,定义一个点与源点相连当且仅当它的性别没变,与汇点相连当且仅当它的性别变了,然后用一组方案的割的费用来刻画一个方案对应的收益,首先对每个点连边 \(S\to i\),容量 \(c_i\)。对于每组繁衍关系我们新建两个点 \(P_1,P_2\) 分别表示同时变和同时不变的收益:
- 对于 \(P_1\),连边 \(S\to P_1\),容量 \(b_i\),再连边 \(P_1\to x,P_1\to y\),容量均 \(\infty\),此时如果 \(P_1\) 与 \(S\) 相连但 \(x,y\) 之一与 \(T\) 相连显然割的权值就无穷大了。
- 对于 \(P_2\) 也同理,连边 \(P_2\to T\),容量 \(\lfloor b_id_i\rfloor\),再连边 \(x\to P_2,y\to P_2\),容量均 \(\infty\)。
然后拿 \(\sum b_i+\lfloor b_id_i\rfloor\) 减去最小割就是答案。
(事实上真实情况是:如果没有系数那么直接每个点都保持原来的性别即可)
现在有了这个系数的存在。乍一看以为需要奇怪的降维技巧,但实际上发现 \(\lfloor 10\ln(1+A)\rfloor\) 是很小的,最多只有 \(53\),因此直接枚举它,剩余部分怎么办呢?\(k\) 很小,而我们逐层转移时只用关心上一层血缘链上的点的状态,一眼状压。\(dp_{i,j,S}\) 表示考虑到第 \(i\) 层,\(j\) 对相邻异性,上一层状态为 \(S\)。转移很容易。代价跑一遍上面的最小割即可。最后再对与血缘链没有任何联系的点跑一遍最小割,把共享加上即可
时间复杂度 \(53·n^2k·4^k+53·n2^k·\Theta(\text{dinic})\),由于 dinic 部分显然跑不满,可以通过。
const int MAXK = 4;
const int MAXN = 50;
const int MAXM = 1000;
const int MAXP = 1e4;
const int MAXNK = 200;
const int MAXMSK = 16;
int n, k, m, p, cst[MAXM + 5]; ll res = 0;
int ch[MAXK + 2][MAXN + 5], dep[MAXM + 5]; char s[MAXM + 5];
struct dat {int u, v, w; double coef;} b[MAXP + 5];
struct ufset {
int f[MAXM + 5];
ufset() {memset(f, 0, sizeof(f));}
int find(int x) {return (!f[x]) ? x : f[x] = find(f[x]);}
} F;
void init_dep() {
for (int i = 1; i <= p; i++) {
int fu = F.find(b[i].u), fv = F.find(b[i].v);
if (dep[fv]) swap(fu, fv); if (fu != fv) F.f[fv] = fu;
}
for (int i = 1; i <= m; i++) dep[i] = dep[F.find(i)];
}
namespace MaxFlow {
const ll INFll = 1e13;
const int MAXV = 1e5;
const int MAXE = 1e6;
int S, T, hd[MAXV + 5], to[MAXE + 5], nxt[MAXE + 5], ec = 1; ll cap[MAXE + 5];
void init_graph() {for (int i = 1; i <= T; i++) hd[i] = 0; ec = 1;}
void adde(int u, int v, ll f) {
to[++ec] = v; cap[ec] = f; nxt[ec] = hd[u]; hd[u] = ec;
to[++ec] = u; cap[ec] = 0; nxt[ec] = hd[v]; hd[v] = ec;
}
int dis[MAXV + 5], now[MAXV + 5];
bool getdep(int S, int T) {
for (int i = 1; i <= T; i++) dis[i] = -1;
queue<int> q; q.push(S); dis[S] = 0;
while (!q.empty()) {
int x = q.front(); q.pop(); now[x] = hd[x];
for (int e = hd[x]; e; e = nxt[e]) {
int y = to[e]; ll z = cap[e];
if (z && !~dis[y]) {dis[y] = dis[x] + 1; q.push(y);}
}
}
return ~dis[T];
}
int getflow(int x, ll f) {
if (x == T) return f; ll ret = 0;
for (int &e = now[x]; e; e = nxt[e]) {
int y = to[e]; ll z = cap[e];
if (z && dis[y] == dis[x] + 1) {
ll w = getflow(y, min(f - ret, z));
ret += w; cap[e] -= w; cap[e ^ 1] += w;
if (f == ret) return ret;
}
}
return ret;
}
ll dinic() {
ll ret = 0;
while (getdep(S, T)) ret += getflow(S, INFll);
return ret;
}
} using namespace MaxFlow;
ll getcst(int x, int msk, int LN) {
int cntv = 0, cnte = 0, idcnt; static int id[MAXM + 5];
for (int i = 1; i <= m; i++) if (dep[i] == x) id[i] = ++cntv;
for (int i = 1; i <= p; i++) if (dep[b[i].u] == x) ++cnte;
S = cntv + cnte * 2 + 1; T = S + 1; idcnt = cntv; ll sum = 0;
init_graph();
for (int i = 1; i <= m; i++) if (dep[i] == x) adde(S, id[i], cst[i]);
for (int i = 1; i <= k; i++) {
int pt = ch[i][x];
if (((msk >> i - 1) & 1) == (s[pt] == 'M')) adde(S, id[pt], INFll); // not change
else adde(id[pt], T, INFll);
}
for (int i = 1; i <= p; i++) if (dep[b[i].u] == x) {
int idu = id[b[i].u], idv = id[b[i].v]; ++idcnt; // both not change
adde(idcnt, idu, INFll); adde(idcnt, idv, INFll);
adde(S, idcnt, b[i].w * LN); sum += b[i].w * LN;
++idcnt; // both change
adde(idu, idcnt, INFll); adde(idv, idcnt, INFll);
adde(idcnt, T, floor(b[i].w * b[i].coef) * LN);
sum += floor(b[i].w * b[i].coef) * LN;
}
return sum - dinic();
}
ll getrst(int LN) {
int cntv = 0, cnte = 0, idcnt; static int id[MAXM + 5];
for (int i = 1; i <= m; i++) if (!dep[i]) id[i] = ++cntv;
for (int i = 1; i <= p; i++) if (!dep[b[i].u]) ++cnte;
S = cntv + cnte * 2 + 1; T = S + 1; idcnt = cntv; ll sum = 0;
init_graph();
for (int i = 1; i <= m; i++) if (!dep[i]) adde(S, id[i], cst[i]);
for (int i = 1; i <= p; i++) if (!dep[b[i].u]) {
int idu = id[b[i].u], idv = id[b[i].v]; ++idcnt; // both not change
adde(idcnt, idu, INFll); adde(idcnt, idv, INFll);
adde(S, idcnt, b[i].w * LN); sum += b[i].w * LN;
++idcnt; // both change
adde(idu, idcnt, INFll); adde(idv, idcnt, INFll);
adde(idcnt, T, floor(b[i].w * b[i].coef) * LN);
sum += floor(b[i].w * b[i].coef) * LN;
}
return sum - dinic();
}
ll dp[MAXN + 5][MAXNK + 5][MAXMSK + 2];
void solve_LN(int LN) {
static ll cst_layer[MAXN + 5][MAXMSK + 2];
// printf("solve_LN %d\n", LN);
for (int i = 1; i <= n; i++) for (int j = 0; j < (1 << k); j++)
cst_layer[i][j] = getcst(i, j, LN);
memset(dp, 0xcf, sizeof(dp));
for (int i = 0; i < (1 << k); i++) dp[1][0][i] = cst_layer[1][i];
for (int i = 2; i <= n; i++) for (int j = 0; j < (1 << k); j++) {
for (int l = 0; l <= (i - 1) * k; l++) for (int s = 0; s < (1 << k); s++)
if (l >= __builtin_popcount(j ^ s))
chkmax(dp[i][l][j], dp[i - 1][l - __builtin_popcount(j ^ s)][s] + cst_layer[i][j]);
}
ll rst = getrst(LN);
for (int i = 0; i <= (n - 1) * k; i++) if (floor(10.0 * log(i + 1)) == LN) {
for (int j = 0; j < (1 << k); j++) chkmax(res, dp[n][i][j] + rst);
}
}
int main() {
scanf("%d%d%d%d%s", &n, &k, &m, &p, s + 1);
for (int i = 1; i <= m; i++) scanf("%d", &cst[i]);
for (int i = 1; i <= k; i++) for (int j = 1; j <= n; j++)
scanf("%d", &ch[i][j]), dep[ch[i][j]] = j;
for (int i = 1; i <= p; i++) scanf("%d%d%d%lf", &b[i].u, &b[i].v, &b[i].w, &b[i].coef);
init_dep();
for (int LN = 0; LN <= 53; LN++) solve_LN(LN);
printf("%lld\n", res);
return 0;
}