luogu P1631 序列合并
又是一道堆的题\(aaaaa\)。最近几天快被数据结构逼疯了。
首先我们有一个定理:\(i<j,a_k+b_i\leq a_k+b_j\)
解法一:
我们先来看一份\(wsj\)的暴力代码:
#include<cstdio>
#include<algorithm>
using namespace std;
int a[100001],b[100001];
int c[30000001];
int n;
int buf[17];
inline void read(int &x) {
char ch=getchar();
x=0;
while(ch<'0') ch=getchar();
while(ch>='0' && ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
}
inline void write(int x) {
if(!x) {
putchar('0');
putchar('\n');
return;
}
register int cnt=0;
while(x)buf[++cnt]=(x%10)+48,x/=10;
while(cnt)putchar(buf[cnt--]);
}
int main() {
freopen("seq.in","r",stdin);
freopen("seq.out","w",stdout);
read(n);
for(int i=1; i<=n; i++)read(a[i]);
for(int i=1; i<=n; i++)read(b[i]);
int k = 0;
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
c[++k] = a[i] + b[j];
}
}
sort(c+1,c+k+1);
for(int i=1; i<=n; i++)
write(c[i]),putchar(' ');
return 0;
}
很暴力,对吗?
对于这份暴力代码,结合结论,做一点优化。
我们维护一个大根堆,堆内为当前最小的\(n\)个数。首先我们让\(a_i+b_1\)无条件入队。并在数组内枚举,若当前\(a_i+b_j\)大于堆顶那么直接跳出当前循环。最后堆中的值倒序输出即可。
但复杂度很不稳定。约为\(O(nlog^2 n^2)\).
解法二:
维护n个队列,每个队列的初始值为整个\(b\)数组。由\(a_i\)领导。将每个\(b_1\)出列,与其这个数组领导的\(a\)相加,进入堆中。这是一个小根堆,每次取出堆顶并将那个堆顶所在的队列的队首加上领导者并加入堆中,像这样循环\(n\)遍。这利用了他的单调性。
代码实现:
#include<cstdio>
#include<queue>
using namespace std;
inline void read(int &x){
char s=getchar();x=0;int f=1;
while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
while(s>='0'&&s<='9') x=(x<<3)+(x<<1)+(s^48),s=getchar();
x*=f;
}
inline void print(int x){
if(x<0){putchar('-');print(-x);return;}
if(x>9) print(x/10);
putchar(x%10+48);
}
struct yyy {
int num,ans;
bool operator<(const yyy &x) const {
return ans> x.ans;
}
}tmp;
int n,m,a[100039],b[100039],s[100039];
priority_queue<yyy> q;
int main() {
// freopen("seq.in","r",stdin);
// freopen("seq.out","w",stdout);
register int i;
scanf("%d",&n);
for(i=1;i<=n;i++) read(a[i]);
for(i=1;i<=n;i++) read(b[i]);
for(i=1;i<=n;i++) s[i]=1,q.push((yyy){i,a[i]+b[1]});
for(i=1;i<=n;i++){
tmp=q.top();
q.pop();
print(tmp.ans);
if(i!=n) putchar(' ');
s[tmp.num]++;
q.push((yyy) {tmp.num,a[tmp.num]+b[s[tmp.num]]});
}
return 0;
}