蓝书(算法竞赛进阶指南)刷题记录——POJ2442 Sequence(贪心+堆)

题目:POJ2442.
题目大意:给定 m m m个长度为 n n n的序列,要求在每一个序列中取出一个数并求和,总共会得到 n m n^m nm个和,现在要求这之中最小的 n n n个.
1 ≤ n ≤ 2 ∗ 1 0 3 , 1 ≤ m ≤ 100 1\leq n\leq 2*10^3,1\leq m\leq 100 1n2103,1m100.

首先先考虑 m = 2 m=2 m=2怎么做.

对于两个序列,我们把这两个序列分别从小到大排序.设排序后两个序列为 a , b a,b a,b,贪心地想到若 k k k小和为 a [ i ] + b [ j ] a[i]+b[j] a[i]+b[j] k + 1 k+1 k+1小和的候选答案中就加入了 a [ i + 1 ] + b [ j ] a[i+1]+b[j] a[i+1]+b[j] a [ i ] + b [ j + 1 ] a[i]+b[j+1] a[i]+b[j+1].

有了这个性质后,我们用一个小根堆来维护一个二元组 ( i , j ) (i,j) (i,j)表示一个候选答案为 a [ i ] + b [ j ] a[i]+b[j] a[i]+b[j],然后每次把堆顶取出后加入 ( i + 1 , j ) (i+1,j) (i+1,j) ( i , j + 1 ) (i,j+1) (i,j+1)两个候选答案.容易发现取出前 n n n小的候选答案集合大小不会超过 O ( n ) O(n) O(n),因此复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn)的.

值得注意的是, ( i , j ) (i,j) (i,j)可以通过 ( i − 1 , j ) (i-1,j) (i1,j) ( i , j − 1 ) (i,j-1) (i,j1)两个答案加入候选集合.为了避免重复,我们钦定对于每一个候选答案 ( i , j ) (i,j) (i,j),若 j j j指针发生了移动则 i i i指针不会再发生移动,这样就可以保证不会重复了.

具体实现的时候我们可以维护一个四元组 ( x , i , j , t a g ) (x,i,j,tag) (x,i,j,tag),其中 x = a [ i ] + b [ j ] x=a[i]+b[j] x=a[i]+b[j]表示一个候选答案, t a g = 0 / 1 tag=0/1 tag=0/1表示 j j j指针移动 / / /没有移动过.

而对于 m > 2 m>2 m>2的情况,我们可以直接把前两个序列得到的 n n n小值作为一个序列,继续与第 3 3 3个序列计算 n n n小值并以此类推…时间复杂度 O ( m n log ⁡ n ) O(mn\log n) O(mnlogn).

代码如下:

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

#define Abigail inline void
typedef long long LL;

const int M=100,N=2000;

struct state{
  int x,i,j,tag;
  state(int X=0,int I=0,int J=0,int Tag=0){x=X;i=I;j=J;tag=Tag;}
  bool operator > (const state &p)const{return x>p.x;} 
};
priority_queue<state,vector<state>,greater<state> >q;
int a[M+9][N+9],n,m,tmp[N+9]; 

Abigail into(){
  scanf("%d%d",&m,&n);
  for (int i=1;i<=m;++i)
    for (int j=1;j<=n;++j)
      scanf("%d",&a[i][j]); 
}

Abigail work(){
  state t;
  sort(a[1]+1,a[1]+1+n);
  for (int k=2;k<=m;++k){
  	int i=1,j=1;
  	sort(a[k]+1,a[k]+1+n);
  	while (!q.empty()) q.pop();
  	q.push(state(a[1][i]+a[k][j],i,j,0));
  	for (int l=1;l<=n;++l){
	  t=q.top();q.pop();
	  tmp[l]=t.x;
	  q.push(state(a[1][t.i]+a[k][t.j+1],t.i,t.j+1,1));
	  if (!t.tag) q.push(state(a[1][t.i+1]+a[k][t.j],t.i+1,t.j,0));
	}
	for (int l=1;l<=n;++l) a[1][l]=tmp[l];
  }
}

Abigail outo(){
  for (int i=1;i<n;++i)
    printf("%d ",a[1][i]);
  printf("%d\n",a[1][n]);
}

int main(){
  int T;
  scanf("%d",&T);
  while (T--){
    into();
    work();
    outo();
  }
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值