[bzoj3532][Sdoi2014]Lis
题目大意:给定序列$A$,序列中的每一项$A_i$有删除代价$B_i$和附加属性$C_i$。请删除若干项,使得$A$的最长上升子序列长度至少减少$1$,且代价最小,并输出方案。若有多种方案,$C$的字典序最小的一种。
题解:如果只要求输出代价,令$f_i$表示到$i$的最长上升子序列长度,把所有$j<i\&\&a_j<a_i\&\&f_i=f_j+1$的$i$和$j$之间连一条边,跑一遍最小割就行了。
但是题目要求字典序最小,可以贪心,按$C$从小到大排序,每次看一条边是否可能是最小割中的边,是的话就删去。
那么就要求较快出一条边是否可以在最小割中。
比如求$(u,v)$是否是可以在最小割中,正常的方法是把这条边的容量减$1$,看最小割的大小是不是减少$1$。但这样很慢,这可以转变为在残量网络上看从$u$出发是否能找到一条$u$到$v$的增广路,如果找不到就说明该边可能在最小割中。
发现,删去一条边后,需要重新计算最大值。
这时可以使用退流,比如把$(u,v)$退流,就可以在残量网络中跑两遍最大流,分别是$(u,start)$和$(end,v)$,这样就可以把这条边的流量的贡献减去
卡点:1.$f$数组没清空
C++ Code:
#include <cstdio> #include <algorithm> #include <cstring> //#define int long long #define maxn 1410 #define maxm 246000 #define X(x) ((x << 1) - 1) #define Y(x) (x << 1) using namespace std; const long long inf = 0x3f3f3f3f3f3f3f3f; int Tim, n; int A[maxn], C[maxn], rnk[maxn], num[maxn]; int ans[maxn], ansn; long long B[maxn]; int f[maxn], maxf = 0; int head[maxn], cnt = 2; struct Edge { int to, nxt; long long w; } e[maxm << 1]; void add(int a, int b, long long c) { e[cnt] = (Edge) {b, head[a], c}; head[a] = cnt; e[cnt ^ 1] = (Edge) {a, head[b], 0}; head[b] = cnt ^ 1; cnt += 2; } inline int max(int a, int b) {return a > b ? a : b;} inline long long min(long long a, long long b) {return a < b ? a : b;} inline bool cmp(int a, int b) {return C[a] < C[b];} int st, ed, tst, ted; int d[maxn]; int q[maxn], h, t; bool bfs() { memset(d, 0, sizeof d); d[q[h = t = 0] = tst] = 1; while (h <= t) { int u = q[h++]; if (u == ted) return true; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (e[i].w && !d[v]) { d[v] = d[u] + 1; q[++t] = v; } } } return d[ted]; } long long dfs(int x, long long low) { // printf("in:%d %lld\n", x, low); if (!low || x == ted) return low; // printf("no:%d %lld\n", x, low); int v; long long res = 0, w; for (int i = head[x]; i; i = e[i].nxt) { v = e[i].to; if (e[i].w && d[v] == d[x] + 1) { w = dfs(v, min(low - res, e[i].w)); e[i].w -= w; e[i ^ 1].w += w; res += w; if (low == res) return low; } } // printf("out:%d %lld\n", x, res); if (!res) d[x] = -1; return res; } long long dinic(int st, int ed) { tst = st, ted = ed; long long ans = 0; while (bfs()) ans += dfs(tst, inf); return ans; } signed main() { scanf("%d", &Tim); while (Tim --> 0) { scanf("%d", &n); st = 0, ed = n << 1 | 1; for (int i = 1; i <= n; i++) scanf("%d", &A[i]); for (int i = 1; i <= n; i++) scanf("%lld", &B[i]); for (int i = 1; i <= n; i++) scanf("%d", &C[i]), rnk[i] = i; for (int i = 1; i <= n; i++) { for (int j = 0; j < i; j++) if (A[j] < A[i]) f[i] = max(f[i], f[j] + 1); // printf("%d %d\n", i, f[i]); maxf = max(maxf, f[i]); } // printf("lalal:%d\n", maxf); for (int i = 1; i <= n; i++) { num[i] = cnt; add(X(i), Y(i), B[i]); if (f[i] == 1) add(st, X(i), inf); if (f[i] == maxf) add(Y(i), ed, inf); for (int j = i + 1; j <= n; j++) { if (A[i] < A[j] && f[i] + 1 == f[j]) add(Y(i), X(j), inf); } } printf("%lld ", dinic(st, ed)); ansn = 0; sort(rnk + 1, rnk + n + 1, cmp); for (int i = 1; i <= n; i++) { int now = rnk[i], E = num[now]; // printf("%d %lld\n", now, e[E].w); if (!e[E].w) { tst = X(now), ted = Y(now); if (!bfs()) { dinic(ed, Y(now)); dinic(X(now), st); e[E].w = e[E ^ 1].w = 0; ans[++ansn] = now; } } } sort(ans + 1, ans + ansn + 1); printf("%d\n", ansn); for (int i = 1; i < ansn; i++) printf("%d ", ans[i]); printf("%d\n", ans[ansn]); if (Tim) { cnt = 2; memset(f, 0, sizeof f); memset(head, 0, sizeof head); maxf = 0; } } return 0; }