【网络流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;
}
经验分享 程序员 微信小程序 职场和发展