洛谷 P2766 最长不下降子序列问题(最大流)

23 篇文章 1 订阅
12 篇文章 0 订阅

题目描述

«问题描述:

给定正整数序列x1,...,xn 。

(1)计算其最长不下降子序列的长度s。

(2)计算从给定的序列中最多可取出多少个长度为s的不下降子序列。

(3)如果允许在取出的序列中多次使用x1和xn,则从给定序列中最多可取出多少个长度为s的不下降子序列。

«编程任务:

设计有效算法完成(1)(2)(3)提出的计算任务。

输入输出格式

输入格式:

 

第1 行有1个正整数n,表示给定序列的长度。接下来的1 行有n个正整数n:x1, ..., xn。

 

输出格式:

 

第1 行是最长不下降子序列的长度s。第2行是可取出的长度为s 的不下降子序列个数。第3行是允许在取出的序列中多次使用x1和xn时可取出的长度为s 的不下降子序列个数。

 

输入输出样例

输入样例#1: 

4
3 6 2 5

输出样例#1: 

2
2
3

说明

n\le 500n≤500

 

解题思路

第一问用dp求出最大不下降子序列长度ss,第二问第三问用最大流解。 把i节点拆成两部分i1,i2,自建源点s,汇点t,dp[i]为1的节点连(s,i1),dp[i]为ss的连(i2,t)。然后连(i1,i2)。如果dp[i] + 1 == dp[j],那么连边(i2, j1),正向弧都为1,反向弧都为0。假设有一个最长不下降子序列为2 4 (均为下标),因为从i1连出的边只能到i2,流过的弧就为(s,2_1),(2_1,2_2),(2_2,4_1),(4_1,4_2),(4_2, t)。只有dp[i]最大的i2才连接了汇点,所以最大流就是可取出个数。第三问就是把(s,1_1)(1_1,1_2)(n1,n2),以及若dp[n]等于ss的(n2,t)的容量变为无限大。

代码如下

#include <iostream>
#include <queue>
#include <cstring>
#include <cmath>
#include <vector>
#define INF 0x3f3f3f3f
#define s 0
#define t 1002
using namespace std;
int a[505];
int dp[505];
vector<int> g[1005];
struct Line{
	int r, w;
	bool dir;
	Line(int r, int w, int d): r(r), w(w), dir(d){	}
};
vector<Line> line;
int deep[1005];
bool bfs()
{
	queue<int> que;
	memset(deep, 0, sizeof(deep));
	que.push(s);
	deep[s] = 1;
	while(!que.empty()){
		int top = que.front();
		que.pop();
		for(int i = 0; i < g[top].size(); i ++){
			int z = g[top][i];
			if(!deep[line[z].r] && line[z].w){
				deep[line[z].r] = deep[top] + 1;
				que.push(line[z].r);
				if(deep[t])
					return true;
			}
		}
	}
	return false;
}
int dfs(int x, int mix)
{
	if(x == t || !mix)
		return mix;
	int ap = 0;
	for(int i = 0; i < g[x].size(); i ++){
		int z = g[x][i];
		if(deep[x] < deep[line[z].r] && line[z].w){
			int p = dfs(line[z].r, min(mix, line[z].w));
			ap += p;
			mix -= p;
			line[z].w -= p;
			line[z^1].w += p;
			if(!mix)
				return ap;
		}
	}
	return ap;
}
int dinic()
{
	int ans = 0;
	while(bfs())
		ans += dfs(s, INF);
	return ans;
}
void add_line(int x, int y, int w)
{
	line.push_back(Line(y, w, 1));
	g[x].push_back(line.size() - 1);
	line.push_back(Line(x, 0, 0));
	g[y].push_back(line.size() - 1);
}
int main()
{
	int n;
	while(cin >> n){
		for(int i = 1; i <= n; i ++)
			cin >>  a[i];
		for(int i = 1; i <= n; i ++)
			dp[i] = 1;
		int ss = 0;
		for(int i = 1; i <= n; i ++){
			for(int j = 1; j < i; j ++){
				if(a[i] >= a[j])
					dp[i] = max(dp[i], dp[j] + 1);
			}
			if(dp[i] > ss)
				ss = dp[i];
		}
		cout << ss << endl;
		for(int i = 1; i <= n; i ++){
			if(dp[i] == 1){
				add_line(s, 2*i, 1);
			}
			if(dp[i] == ss){   //不能else if,因为ss可能等于1 
				add_line(2*i+1, t, 1);
			}
			add_line(2*i, 2*i+1, 1);
		}
		for(int i = 1; i <= n; i ++){
			if(dp[i] == ss)
				continue;
			for(int j = i + 1; j <= n; j ++){
				if(dp[i] + 1 == dp[j] && a[i] <= a[j])
					add_line(2*i+1, 2*j, 1);			
			}
		}
		int q = dinic();
		cout << q << endl;
		add_line(s, 2, INF);
		add_line(2, 3, INF);
		add_line(2*n, 2*n+1, INF);
		if(dp[n] == ss)
			add_line(2*n+1, t, INF);		
		cout << dinic() + q << endl;   //累加的答案 
		for(int i = s; i <= t; i ++)
			g[i].clear();
		line.clear(); 
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值