歸併法的另類活用
歸併排序是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--;
}
}