【网络流24题】 最长不下降子序列问题
题意:
给定正整数序列 x 1 , . . . , x n x_1,...,x_n x1,...,xn
(1)计算其最长不下降子序列的长度 s s s。
(2)计算从给定的序列中最多可取出多少个长度为s的不下降子序列。
(3)如果允许在取出的序列中多次使用 x 1 x_1 x1 和 x n x_n xn,则从给定序列中最多可取出多少个长度为 s s s 的不下降子序列。
分析
第一问,普及组问题。 第二,三问,要用网络流解决。
第二问
建立超级源点 S S S 和超级汇点 T T T 我们为了让 S S S 到 T T T 的路径是一条满足长度为 s s s 的最长不下降子序列,于是要将 f j = f i + 1 f_j=f_i + 1 fj=fi+1 的 i i i 向 j j j 连一条边。这样子从 s s s 到 t t t 的一个流表示的是一个长度为 s s s 的最长不下降子序列。 可问题来了,每个点只能走一次,这个怎么解决呢? 有一个常用的技巧,就是将点变成边,具体实现是将点变成拆成两个点,中间连一条容量为 1 1 1 的边,表示这条边只能走一次。 然后连边细节如下: ①:对每个点 i i i,从 i i i 到 i ′ i i′ 连一条边 ②:若 f i = = 1 f_i == 1 fi==1,从 s s s 到 i i i 连一条边 ③: a i ≤ a j 且 f j = f i + 1 a_i leq a_j且f_j = f_i + 1 ai≤aj且fj=fi+1,从 i ′ i i′ 到 j j j 连一条边 ④:若 f i = a n s 1 f_i = ans1 fi=ans1,从 i ′ i i′ 到 t t t 连一条边 上述每条边容量都为 1 1 1
第三问
将 s s s 到 1 1 1, 1 1 1 到 1 ′ 1 1′ 的边权改为 i n f inf inf 如果 f n = a n s 1 f_n = ans1 fn=ans1,将 n n n 到 n ′ n n′, n ′ n n′ 到 t t t 的边权改为 i n f inf inf
代码如下
#include <bits/stdc++.h>
#define N 100005
#define inf 2147483647
using namespace std;
struct node{
int a, b, c, n;
}d[N * 2];
int dep[N], h[N], cur[N], a[N], f[N], ans, cnt = 1, tot, s, t, ans1;
void cr(int a, int b, int c){
d[++cnt].a = a; d[cnt].b = b; d[cnt].c = c; d[cnt].n = h[a]; h[a] = cnt;
}
void lk(int a, int b, int c){
cr(a, b, c);
cr(b, a, 0);
}
int bfs(){
int i, a, b, c;
memset(dep, 0, sizeof(dep));
for(i = 0; i <= tot; i++) cur[i] = h[i];
queue<int> q;
q.push(s);
dep[s] = 1;
while(!q.empty()){
a = q.front();
q.pop();
for(i = h[a]; i; i = d[i].n){
b = d[i].b;
c = d[i].c;
if(!dep[b] && c){
dep[b] = dep[a] + 1;
q.push(b);
}
}
}
return dep[t];
}
int dfs(int a, int flow){
int i, b, c, w, used = 0;
if(a == t) return flow;
for(i = cur[a]; i; i = d[i].n){
cur[a] = i;
b = d[i].b;
c = d[i].c;
if(dep[b] == dep[a] + 1 && c > 0){
if(w = dfs(b, min(flow - used, c))){
used += w;
d[i].c -= w;
d[i ^ 1].c += w;
}
if(used == flow) break;
}
}
return used;
}
int main(){
int i, j, n, m;
scanf("%d", &n);
s = 2 * n + 1, t = 2 * n + 2;
tot = 2 * n + 2;
for(i = 1; i <= n; i++) scanf("%d", &a[i]), f[i] = 1;
for(i = 1; i <= n; i++){
for(j = 1; j < i; j++) if(a[i] >= a[j]) f[i] = max(f[i], f[j] + 1);
ans1 = max(ans1, f[i]);
}
for(i = 1; i <= n; i++){
if(f[i] == 1) lk(s, i, 1);
if(f[i] == ans1) lk(i + n, t, 1);
lk(i, i + n, 1);
}
for(i = 1; i <= n; i++){
for(j = 1; j < i; j++){
if(a[j] <= a[i] && f[i] == f[j] + 1) lk(j + n, i, 1);
}
}
printf("%d
", ans1);
while(bfs()) ans += dfs(s, inf);
printf("%d
", ans);
lk(s, 1, inf), lk(1, 1 + n, inf);
if(f[n] == ans1) lk(n, n + n, inf), lk(n + n, t, inf);
while(bfs()) ans += dfs(s, inf);
printf("%d
", ans);
return 0;
}
