HDU6611 K Subsequence - 费用流

K Subsequence

Time Limit: 2000/2000 MS (Java/Others)   Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 217   Accepted Submission(s): 28

Problem Description

Master QWsin is dating with Sindar. And now they are in a restaurant, the restaurant has n dishes in order. For each dish, it has a delicious value ai. However, they can only order k times. QWsin and Sindar have a special ordering method, because they believe that by this way they can get maximum happiness value.

Specifically, for each order, they will choose a subsequence of dishes and in this subsequence, when i<j, the subsequence must satisfies ai≤aj. After a round, they will get the sum of the subsequence and they can’t choose these dishes again.

Now, master QWsin wants to know the maximum happiness value they can get but he thinks it’s too easy, so he gives the problem to you. Can you answer his question?

Input

There are multiple test cases. The first line of the input contains an integer T, indicating the number of test cases. For each test case:

First line contains two positive integers n and k which are separated by spaces.

Second line contains n positive integer a1,a2,a3…an represent the delicious value of each dish.

1≤T≤5

1≤n≤2000

1≤k≤10

1≤ai≤1e5

Output

Only an integer represent the maximum happiness value they can get.

Sample Input

1

9 2
5 3 2 1 4 2 1 4 6

Sample Output

22

Source

2019 Multi-University Training Contest 3

 
 

题目大概意思:

给出一个长度为 N ( 1 ≤ N ≤ 2000 ) N(1≤N≤2000) N(1N2000) 的整数序列,第 i i i 个整数的值为 a i ( 1 ≤ a i ≤ 1 0 5 ) a_i(1≤a_i≤10^5) ai(1ai105) ,要求选出至多 k ( 1 ≤ k ≤ 10 ) k(1≤k≤10) k(1k10) 个没有公共元素的非降序子序列,即对于每个子序列,满足 a i ≤ a j ( i &lt; j ) a_i≤a_j(i&lt;j) aiaj(i<j) ,问这些子序列的和最大为多少。

 
 

分析:

k = 1 k=1 k=1 的简单情况下,问题退化为求序列的最大非降序子序列的和。这个问题我们可以使用动态规划的方法在时间复杂度 O ( n 2 ) O(n^2) O(n2) 下求出,即设 d p [ i ] dp[i] dp[i] 为以第 i i i 个元素为首元素的最大非降子序列的和,那么容易得到状态转移方程为:

d p [ i ] = m a x ( d p [ j ] ) + a i , i &lt; j dp[i]=max(dp[j])+a_i,i&lt;j dp[i]=max(dp[j])+ai,i<j a i ≤ a j a_i≤a_j aiaj

可是当 k &gt; 1 k&gt;1 k>1 时,我们是无法简单地通过进行 k k k 次贪心选取和最大的非降子序列来得到最优解的,例如下面这组数据:

N = 10 , k = 2 , { a i } = { 1 , 2 , 3 , 4 , 3 , 2 , 1 , 2 , 3 , 4 } N=10,k=2,\{a_i\}=\{1,2,3,4,3,2,1,2,3,4\} N=10,k=2,{ai}={1,2,3,4,3,2,1,2,3,4}

第一次选取时,和最大的子序列为: { a 1 , a 2 , a 3 , a 5 , a 9 , a 10 } = { 1 , 2 , 3 , 3 , 3 , 4 } \{a_1,a_2,a_3,a_5,a_9,a_{10}\}=\{1,2,3,3,3,4\} {a1,a2,a3,a5,a9,a10}={1,2,3,3,3,4} ,和为 16 16 16 .

第二次选取时,和最大的子序列为: { a 6 , a 8 } = { 2 , 2 } \{a_6,a_8\}=\{2,2\} {a6,a8}={2,2} ,和为 4 4 4 .

最终我们会得到 20 20 20 的错误答案,而实际上,选出的两个子序列应该为:

{ a 1 , a 2 , a 3 , a 4 , a 10 } = { 1 , 2 , 3 , 4 , 4 } \{a_1,a_2,a_3,a_4,a_{10}\}=\{1,2,3,4,4\} {a1,a2,a3,a4,a10}={1,2,3,4,4} ,和为 14 14 14 .

{ a 6 , a 8 , a 9 } = { 2 , 2 , 3 } \{a_6,a_8,a_9\}=\{2,2,3\} {a6,a8,a9}={2,2,3} 和为 7 7 7 .

正确答案是 21 21 21 .

于是我们观察这些子序列,贪心法选出的第一条子序列是 { a 1 , a 2 , a 3 , a 5 , a 9 , a 10 } = { 1 , 2 , 3 , 3 , 3 , 4 } \{a_1,a_2,a_3,a_5,a_9,a_{10}\}=\{1,2,3,3,3,4\} {a1,a2,a3,a5,a9,a10}={1,2,3,3,3,4} ,选择了最优解中不会选择的 a 5 = 3 a_5=3 a5=3 ;原序列中还剩下 { a 4 , a 6 , a 7 , a 8 } = { 4 , 2 , 1 , 2 } \{a_4,a_6,a_7,a_8\}=\{4,2,1,2\} {a4,a6,a7,a8}={4,2,1,2} , 缺少了最优解的第二个子序列中的 a 9 = 3 a_9=3 a9=3 ,如果我们把贪心法选出的第一条子序列中的 { a 5 , a 9 } \{a_5,a_9\} {a5,a9} 删去,添加 a 4 = 4 a_4=4 a4=4 ,再选择 { a 6 , a 8 , a 9 } \{a_6,a_8,a_9\} {a6,a8,a9} 作为新的子序列,那么就可以得到最优解了。这种删去前一次遍历的最优解中的元素来构造总体最优解的思想,可以使用费用流来实现。由于这里是求最大和,那么我们可以通过求边权取负的最小费用流来计算最大费用流:

  1. 把每个序列中的每个元素拆分为 2 2 2 个节点(出节点和入节点),连一条出节点到入节点的容量为 1 1 1 ,费用为 − a i -a_i ai 的边;

  2. 遍历序列,对每一对 i &lt; j i&lt;j i<j a i ≤ a j a_i≤a_j aiaj 的元素,连一条 i i i 的出节点到 j j j 的入节点的容量为 1 1 1 ,费用为 0 0 0 的边;

  3. 从超级源点向每一个入节点连容量为 1 1 1 ,费用为 0 0 0 的边,从每一个出节点向超级汇点连一条容量为 1 1 1 ,费用为 0 0 0 的边。

  4. 求从超级源点到超级汇点的流量为 k k k 的最小费用,将该费用取负即为所求答案。

 
 

需要注意的是,由于 N N N 可能高达 2 × 1 0 3 2×10^3 2×103 ,边的数量可能高达 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n1) ,在计算最小费用流时若采用 B e l l m a n − F o r d Bellman-Ford BellmanFord 算法 或是 S P F A SPFA SPFA 算法 求最小费用路径则很有可能会超出时间限制,因此这里应在初始时使用 S P F A SPFA SPFA 算法 求出初始势,再使用 D i j k s t r a Dijkstra Dijkstra 算法 求此后的最小费用路径。

 
 
下面贴代码:

#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;


const int INF = 1 << 28;
const int MAX_V = 4050;

struct P
{
	int d;
	int num;
	bool operator < (const P& y)const
	{
		return d > y.d;
	}
};

struct Edge
{
	int to;
	int cap;
	int cost;
	int rev;
};
vector<Edge> G[MAX_V];
int h[MAX_V];
int dist[MAX_V];
int prevv[MAX_V], preve[MAX_V];

void add_edge(int from, int to, int cap, int cost);

int min_cost_flow(int s, int t, int f, int V);

int _min(const int& a, const int& b);

int A[MAX_V];

int main()
{
	int T, N, K;
	scanf("%d", &T);

	while (T--)
	{
		scanf("%d%d", &N, &K);

		for (int i = 0; i < N; ++i)
		{
			scanf("%d", A + i);
		}
		int mS = 2 * N, mT = 2 * N + 1;
		for (int i = 0; i < N; ++i)
		{
			const int& cur = A[i];
			add_edge(mS, i, 1, 0);
			add_edge(i, N + i, 1, -cur);
			add_edge(N + i, mT, 1, 0);
			for (int j = i + 1; j < N; ++j)
			{
				if (A[j] >= cur)
				{
					add_edge(N + i, j, 1, 0);
				}
			}
		}

		printf("%d\n", -min_cost_flow(mS, mT, K, 2 * N + 2));

		for (int i = 2 * N + 1; i >= 0; --i)
		{
			G[i].clear();
		}
	}


	return 0;
}

void add_edge(int from, int to, int cap, int cost)
{
	Edge e1 = { to,cap,cost,G[to].size() };
	Edge e2 = { from,0,-cost,G[from].size() };

	G[from].push_back(e1);
	G[to].push_back(e2);
}

int min_cost_flow(int s, int t, int f, int V)
{
	P m, ms;
	int res = 0;

	queue<int> q;
	fill(h, h + V, INF);
	h[s] = 0;
	q.push(s);
	while (!q.empty())
	{
		int v = q.front();
		q.pop();
		dist[v] = 0;

		int deg = G[v].size();
		for (int i = 0; i < deg; i++)
		{
			Edge& e = G[v][i];
			if (e.cap)
			{
				int d = h[v] + e.cost;
				if (d < h[e.to])
				{
					h[e.to] = d;
					if (!dist[e.to])
					{
						dist[e.to] = 1;
						q.push(e.to);
					}
				}
			}
		}
	}

	while (f)
	{
		priority_queue<P> que;

		fill(dist, dist + V, INF);

		dist[s] = ms.d = 0;
		ms.num = s;
		que.push(ms);

		while (!que.empty())
		{
			m = que.top();
			que.pop();

			if (dist[m.num] == m.d)
			{
				int deg = G[m.num].size();
				for (int i = 0; i < deg; i++)
				{
					Edge& e = G[m.num][i];
					if (e.cap)
					{
						ms.num = e.to;
						ms.d = m.d + e.cost + h[m.num] - h[ms.num];
						if (ms.d < dist[ms.num])
						{
							dist[ms.num] = ms.d;
							prevv[ms.num] = m.num;
							preve[ms.num] = i;
							que.push(ms);
						}
					}
				}
			}
		}

		if (dist[t] == INF)
		{
			return -1;
		}

		for (int v = 0; v < V; v++)
		{
			h[v] += dist[v];
		}

		int d = f;
		for (int v = t; v != s; v = prevv[v])
		{
			d = _min(d, G[prevv[v]][preve[v]].cap);
		}
		f -= d;

		res += d * h[t];

		for (int v = t; v != s; v = prevv[v])
		{
			Edge& e = G[prevv[v]][preve[v]];
			e.cap -= d;
			G[v][e.rev].cap += d;
		}
	}
	return res;
}

int _min(const int& a, const int& b)
{
	return a < b ? a : b;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值