Address
https://www.lydsy.com/JudgeOnline/problem.php?id=3532
Solution
先求
表示以
为结尾的最长上升子序列长度,
为整个序列的最长上升子序列长度。
(1)由源点
向所有满足
的点
连容量为
的边。
(2)由所有满足
的点
向汇点
连容量为
的边。
(3)对于一对
,如果
并且
,就连边
,容量
。
问题转化成删掉一些点使得
到
没有路径。
将每个点拆成两个(
和
),原先连向该点的边都连向
,原先由该点连出的边都由
连出,从
到
连容量
的边。
于是删点转化成割边。求最小割(最大流)即可得到第一问的答案。
难点在第二问。字典序显然可以贪心。
从小到大枚举
,如果边
可以出现在最小割中就选
,否则考虑下一个。
回顾 AHOI 2009 Mincut 最小割……
边
可能出现在最小割中,当且仅当删掉边
之后最小割减小的容量为
。
证明:如果删掉边
之后最小割容量减少的值不足
,那么这个割的方案再加上边
之后的容量一定超过了原来的最小割,这时候
不能出现在最小割中,否则最小割容量减小了
,则加上了
后恰好是最小割,故可以出现在最小割中。
删掉边后重新跑一遍最大流,会 TLE 。
等价地,如果残量网络中有从
到
的路径,那么即使
边被删掉,
仍然可以通过
到
的路径传输至少
的流量到
,这样最小割减少的量就不是
了。
故边
能出现在最小割中当且仅当残量网络中没有
到
的路径。
每次选择了
之后,我们需要将边
删掉重新跑最大流,还是 TLE 。
但我们考虑到:删掉
只会影响
到
的增广路集合和
到
的增广路集合。
考虑退流:从
到
跑一遍最大流,再由
到
跑一遍最大流。看上去跑了两次,但是流过的边集变小了,故复杂度可以接受。
Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Edge(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
#define NF(u) for (int &e = cur[u], v = go[e]; e; e = nxt[e], v = go[e])
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 705, M = 1414, L = 1e6 + 5;
const ll INF = 1ll << 60;
int n, a[N], b[N], ecnt, nxt[L], adj[M], st[L], cur[M], go[L],
lev[M], len, que[M], f[N], vis[M], dalao, cnt, ans[N];
ll cap[L];
struct cyx {
int id, x;
} c[N];
bool comp(cyx a, cyx b) {
return a.x < b.x;
}
void add_edge(int u, int v, ll w) {
nxt[++ecnt] = adj[u]; adj[u] = ecnt; st[ecnt] = u;
go[ecnt] = v; cap[ecnt] = w;
nxt[++ecnt] = adj[v]; adj[v] = ecnt; st[ecnt] = v;
go[ecnt] = u; cap[ecnt] = 0;
}
bool bfs(int S, int T) {
int i;
For (i, 1, (n << 1) + 2) lev[i] = -1, cur[i] = adj[i];
lev[que[len = 1] = S] = 0;
For (i, 1, len) {
int u = que[i];
Edge(u) if (cap[e] && lev[v] == -1) {
lev[que[++len] = v] = lev[u] + 1;
if (v == T) return 1;
}
}
return 0;
}
ll dinic(int T, int u, ll flow) {
if (u == T) return flow;
ll res = 0, delta;
NF(u) if (cap[e] && lev[u] < lev[v]) {
delta = dinic(T, v, min(cap[e], flow - res));
if (delta) {
cap[e] -= delta; cap[e ^ 1] += delta;
res += delta; if (res == flow) break;
}
}
if (res != flow) lev[u] = -1;
return res;
}
ll maxflow(int S, int T) {
ll res = 0;
while (bfs(S, T)) res += dinic(T, S, INF);
return res;
}
int which(int x, int y) {
return 2 + (x - 1 << 1) + y;
}
bool check(int S, int T) {
int i;
vis[que[len = 1] = S] = ++dalao;
For (i, 1, len) {
int u = que[i];
Edge(u) {
if (!cap[e] || vis[v] == dalao) continue;
que[++len] = v;
vis[v] = dalao;
if (v == T) return 1;
}
}
return 0;
}
void work() {
int i, j, lis = cnt = 0;
n = read();
For (i, 1, n) a[i] = read();
For (i, 1, n) b[i] = read();
For (i, 1, n) c[c[i].id = i].x = read();
sort(c + 1, c + n + 1, comp);
ecnt = 1;
For (i, 1, (n << 1) + 2) adj[i] = 0;
For (i, 1, n) {
f[i] = 1;
For (j, 1, i - 1) if (a[j] < a[i])
f[i] = max(f[i], f[j] + 1);
lis = max(lis, f[i]);
}
For (i, 1, n) add_edge(which(i, 0), which(i, 1), b[i]);
For (i, 1, n) {
if (f[i] == 1) add_edge(1, which(i, 0), INF);
if (f[i] == lis) add_edge(which(i, 1), (n << 1) + 2, INF);
}
For (i, 1, n) For (j, i + 1, n)
if (a[i] < a[j] && f[i] + 1 == f[j])
add_edge(which(i, 1), which(j, 0), INF);
ll sum = maxflow(1, (n << 1) + 2);
For (i, 1, n) {
int e = c[i].id << 1, u = st[e], v = go[e];
if (check(u, v)) continue;
ans[++cnt] = c[i].id;
maxflow((n << 1) + 2, v); maxflow(u, 1);
}
printf("%lld %d\n", sum, cnt);
sort(ans + 1, ans + cnt + 1);
For (i, 1, cnt) printf("%d ", ans[i]);
printf("\n");
}
int main() {
int T = read();
while (T--) work();
return 0;
}