归并排序是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--;
}
}