POJ2442 Sequence【二叉堆】

Given m sequences, each contains n non-negative integer. Now we may select one number from each sequence to form a sequence with m integers. It’s clear that we may get n ^ m this kind of sequences. Then we can calculate the sum of numbers in each sequence, and get n ^ m values. What we need is the smallest n sums. Could you help us?
给定m个长度为n的序列,从每个序列各取一个数(共n个)求和,显然可以构成 n m n^m nm个和,求其中最小的n个。0 < m <= 100, 0 < n <= 2000

Input

The first line is an integer T, which shows the number of test cases, and then T test cases follow. The first line of each case contains two integers m, n (0 < m <= 100, 0 < n <= 2000). The following m lines indicate the m sequence respectively. No integer in the sequence is greater than 10000.

Output

For each test case, print a line with the smallest n sums in increasing order, which is separated by a space.


题目分析:

先从m=2的简单情况分析
设两个序列为a和b,且a,b已升序排序

不难想到最小和一定是a[1]+b[1]
那么次小和就是min( a[1]+b[2], a[2]+b[1])
假设次小和是a[2]+b[1]
那么第三小的就是min( a[1]+b[2], a[2]+b[2], a[3]+b[1])

从上述规律可以发现
如果a[i]+b[j]为第k小
那么a[i+1]+b[j], a[i]+b[j+1]就会成为第k+1小的备选答案

到这里也就不难想到用一个**初始只有a[1]+b[1]**的二叉堆来维护找前n小和的过程

但是按上述做法
如果a[1]+b[2], a[2]+b[1]都确定为前n小中的一个
那么a[2]+b[2] 就会重复入堆

那么我们在入堆的参数中在加入一个参数pre
pre表示当前是否是由上一状态(i,j)由(i,j+1)得到,这句话十分重要要认真理解

所以初始时堆中只有(1,1,false)
每次取出堆顶(i,j,pre)
插入(i,j+1,true),如果pre==false,在插入(i+1,j,false)
重复n次即得到两个序列的前n小和

对于m个序列
可以先求出前两个序列的前n小和
用这n小和组成一个新序列,再与第三个序列处理产生新的前n小和
反复m次最终得到所求答案


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

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return x*f;
}

const int maxn=5010;
int t;
int n,m;
int a[maxn],b[maxn],c[maxn],cnt;
struct node{int x,y,sum,pre;}mi[maxn];

void swapp(int x,int y)
{
    node tp=mi[x];
    mi[x]=mi[y]; mi[y]=tp;
}

void ins(int x,int y,int pre)
{
    mi[++cnt].x=x; mi[cnt].y=y;
    mi[cnt].pre=pre; mi[cnt].sum=a[x]+b[y];
    int p=cnt;
    while(p>1)
    {
        if(mi[p].sum<mi[p>>1].sum){ swapp(p,p>>1); p>>=1;}
        else break;
    }
}

void pop()
{
    mi[1]=mi[cnt--];
    int p=1,s=p<<1;
    while(s<=cnt)
    {
        if(s<cnt&&mi[s].sum>mi[s+1].sum) s++;
        if(mi[s].sum<mi[p].sum){ swapp(s,p); p=s; s=p<<1;}
        else break;
    }
}

int main()
{
    t=read();
	while(t--)
	{
		m=read();n=read(); cnt=0;
    	for(int i=1;i<=n;++i) c[i]=read();
    	sort(c+1,c+1+n);
    
    	for(int i=1;i<m;++i)
    	{
        	for(int j=1;j<=n;++j) a[j]=c[j],b[j]=read();
        	sort(b+1,b+1+n);
        
        	cnt=0; ins(1,1,0);//初始化
        	for(int j=1;j<=n;++j)
        	{
            	node tp=mi[1]; pop();//取出堆顶(i,j,pre)
            	int x=tp.x, y=tp.y, sum=tp.sum, pre=tp.pre;
            	ins(x,y+1,1);//插入(i,j+1,true)
            	if(pre==0) ins(x+1,y,0);//如果pre==0,插入(i+1,j,false)
            	c[j]=sum;//记录新的前n小和
        	}
    	}
    	for(int i=1;i<=n;++i) 
    	printf("%d ",c[i]);
    	printf("\n");
	}
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值