例题3.4 K个最小和 UVa11997

1.题目描述:点击打开链接

2.解题思路:本题是多路归并问题,即有n个有序表,需要将这n个有序表合成一个有序表。多路归并问题的一般解法是利用优先队列解决,优先队列q中初始存入每个表的第一个元素,然后每次出一个元素,就把它放入新表,并将它所在的表的下一个元素值加入队列。

本题的关键点在于如何将这n组元素转化为上述多路归并问题。我们先考虑一个简化版本的问题,给出两个长度为n的有序表A和B,分别在A和B中任取一个数并相加,可以得到你n^2个和,求这些和中最小的n个和。我们可以把这n^2个和组织成如下n个有序表:

表1:A1+B1≤A1+B2≤A1+B3≤...

表2:A2+B1≤A2+B2≤A2+B3≤...

表3:A3+B1≤A3+B2≤A3+B3≤...

......

表n:An+B1≤An+B2≤An+B3≤...

这样,每个表的元素都是一个和值,按照上述的多路归并的解法合并后输出前n个和值即可。我们用二元组(s,b)表示一个元素,其中s=A[a]+B[b]。这里有一个技巧:无需保存a的下标。因为下一个二元组(s',b+1)可以通过递推式s'=A[a]+B[b+1]=A[a]+B[b]-B[b]+B[b+1]=s-B[b]+B[b+1]。并不需要知道a的下标。

推而广之,现在有n个表,怎么办呢?两两合并即可!比如表A[0],A[1]先合并,新表还是A[0](可以对表A[0]又读又写,因为找下一个s值不需要用到A[0]表中的元素了),这样合并后,表A[0]中的元素是前两个表的n^2个和值,接下来再和表A[2]合并,合并后表A[0]中的元素是前三个表的n^3个和值,以此类推即可。

不过本题有一个重要的技巧:无需把所有的n^n个和值都放到新表中,每次合并都只读取n个元素,并只放入n个元素到新表中即可。这样一来,每次合并的时间复杂度均为O(N*logN),进行n次合并的时间复杂度是O(N^2*logN)。

3.代码:

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

struct Item
{
	int s, b;
	Item(int s, int b) :s(s), b(b){}
	bool operator <(const Item&rhs)const
	{
		return s>rhs.s;
	}
};
void merge(int*A, int*B, int*C, int n)
{
	priority_queue<Item>q;
	for (int i = 0; i < n; i++)
		q.push(Item(A[i] + B[0], 0));//初始化
	for (int i = 0; i < n; i++)//一共只读取前n个元素,注意,不要以为初始化的前n个元素就是最小的
	{
		Item item = q.top(); q.pop();//取出A[a]+B[b]
		C[i] = item.s;
		int b = item.b;
		if (b + 1 < n)//存在下一个元素
			q.push(Item(item.s - B[b] + B[b + 1], b + 1));
	}
}
#define N 768
int A[N][N];
int main()
{
	//freopen("t.txt", "r", stdin);
	int n;
	while (~scanf("%d", &n))
	{
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < n; j++)
				cin >> A[i][j];
			sort(A[i], A[i] + n);
		}
		for (int i = 1; i < n; i++)//两两合并
			merge(A[0], A[i], A[0], n);

		printf("%d", A[0][0]);
		for (int i = 1; i < n; i++)
			printf(" %d", A[0][i]);
		printf("\n");
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值