1. 程式人生 > >歸併法的另類活用

歸併法的另類活用

歸併排序是OI中的一種常用方法,因為它藉助了對於兩個有序佇列,合併成一個有序佇列,只需要看隊頭,並以此達到O(n)的合併複雜度,至於分治只是活用它的一種方式罷了。
但是看隊頭的思想並不只侷限於佇列。
比如說醜數這個經典題:


對於一給定的素數集合 S = {p1, p2, …, pK},考慮一個正整數集合,該集合中任一元素的質因數全部屬於S。這個正整數集合包括,p1、p1p2、p1p1、p1p2p3…(還有其它)。該集合被稱為S集合的“醜數集合”。注意:我們認為1不是一個醜數,你的工作是對於輸入的集合S去尋找“醜數集合”中的第N個“醜數”。


可能有人會問,這哪裡有佇列呢?
實際上是有的,不難發現,每一個醜數都可以寫成,它小的醜數或者1乘上pi(1<=i<=k),那麼如果我們已經求出了前i個醜數a1~ai,第i+1個醜數就一定可以表示為ar * pj(1<=r<=i && 1<=j<=k),那麼我們只需要求前i個醜數乘上每一個p的積中大於第i個醜數的最小值,即是第i+1個醜數。
那麼實際上我們就有了k個佇列,第k個是a1 * pk , a2 * pk,…ai * pk
那麼我們就可以像歸併排序一樣,記錄第k個佇列前幾項被選過了。
當k很大的時候可以用堆或優先佇列來維護,時間複雜度我只能說<O(nk)

Code:

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

int n,k,p[105],seq[100005],cf[105];
int s(int now){
	return p[now]*seq[cf[now]];
}

int heap[105],pos[105],size;
void swap(int u,int v){
	heap[u]^=heap[v],heap[v]^=heap[u],heap[u]^=heap[v];
	pos[heap[u]]=u,pos[heap[v]]=v;
}

void shift(int now){
	while(s(heap[now>>1])>s(heap[now])) swap(now,now>>1);
}

void push(int now){
	heap[++size]=now;
	pos[now]=size;
	shift(size);
}

void modown(int now){
	int nxt;
	while(now<<1<=size){
        nxt=now<<1;
        if(s(heap[nxt])>s(heap[nxt+1]) && nxt+1<=size) nxt++;
        if(s(heap[nxt])>s(heap[now])) return;
		if(nxt>size) return;
        swap(now,nxt);
        now=nxt;
    }
}

int main(){
	int val;
	scanf("%d%d",&k,&n);
	seq[0]=1;
	for(int i=1;i<=k;i++) scanf("%d",&p[i]),push(i);
	for(int i=1;i<=n;i++)
	{
		val=s(heap[1]);
		seq[i]=val;
		while(size && s(heap[1])==val){
			cf[heap[1]]++;
			modown(1);
		}
	}
	printf("%d",seq[n]);
}

再看這樣一道題:(NOI.AC #64. sort)


sort
題目描述
給定兩個長度為 n 的陣列 A 和 B,對於所有的 ai+bj 從小到大排序,並輸出第 L個到第 R 個數。

輸入格式
第一行三個數 n,L,R 。

第二行一共 n 個數,描述 A 。

第三行一共 n 個數,描述 B 。

輸出格式
按順序輸出第L 個到第 R 個數。

樣例
輸入
2 1 4
1 3
2 4
輸出
3 5 5 7
資料範圍
n≤105,1≤L≤R≤n2,R−L<105,1≤ai,bi≤109n≤105,1≤L≤R≤n2,R−L<105,1≤ai,bi≤109 。


求第L個數字的大小很簡單,二分就行。
求L~R的每個數字,將a,b排序
然後我們就有了n個佇列,第i個佇列的元素是:
ai + b1 , ai + b2 , ai + b3…ai + bn
然後就求大於第L個數字的R-L+1個數就行。

Code:

#include<bits/stdc++.h>
#define maxn 100005
#define LL long long
using namespace std;

LL l,r,n;
int a[maxn],b[maxn];
struct node
{ 
	int sum,id;
	node(int a=0,int b=0):sum(a),id(b){}
	bool operator<(const node &B)const{ return sum==B.sum ? id < B.id : sum > B.sum; }
};
priority_queue<node>q;

int main()
{
	scanf("%lld%lld%lld",&n,&l,&r);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) scanf("%d",&b[i]);
	sort(a+1,a+1+n),sort(b+1,b+1+n);
	
	LL L = 0 , R = 2000000000 , Mid;
	for(;L<R;)
	{
		Mid = (L+R) >> 1;
		LL ret = 0;
		for(int i=1,j=n;i<=n;i++)
		{
			for(;j>=1 && a[i] + b[j] > Mid;j--);
			ret += j;
		}
		if(ret >= l) R = Mid;
		else L = Mid + 1;
	}
	
	for(int i=1,j=n;i<=n;i++)
	{
		for(;j>=1 && a[i] + b[j] >= L;j--);
		if(j<n)
			q.push(node(a[i]+b[j+1],j+1));
	}
	
	bool flag = 0;
	LL ans = r-l+1;
	for(int sum,id;ans;)
	{
		sum = q.top().sum , id = q.top().id , q.pop();
		if(flag) printf(" ");
		else flag = 1;
		printf("%d",sum);
		if(id<n)q.push(node(sum-b[id]+b[id+1],id+1));
		ans--;
	}
}