序列
给出m个长度为n的序列,第i个序列为\(\{a[i][j]\}\),现在从m个序列中分别各自取出一个数,求这些数的和的前n小值,\(0<m≤1000,0<n≤2000\)。
解
显然需要对每个序列进行从小到大排序,于是对于两个序列\(\{a_i\},\{b_i\}\)研究,显然最小值为\(a_1+b_1\),而次小值的决策集合容易知道为\(a_2+b_1,a_1+b_2\),如果确定了次小值的决策为\(a_2+b_1\),那么次次小值的决策集合增加了\(a_3+b_1,a_2+b_2\),于是发现对于当前决策了\(\{a_i,b_j\}\),就需要删掉该个决策增加决策\(a_{i+1},b_j\ or\ a_i,b_{j+1}\),
显然这样会决策出现重复,没人愿意花空间去开check数组,于是发现这个问题\(i,j+1,i+1,j\),就类似与网格图上从左上角出发,每次只能向下走或者向右走,因此我们只要如果一个决策的\(j!=1\)的话,就不允许它i增加,这样正好不重不漏,问题在于有些决策暂时没有入集合,会照成影响吗?
比如\(a_i,b_j\),其中j不为1,显然选择它后只会增加决策\(a_i,b_{j+1}\),而不会增加决策\(a_{i+1},b_j\),但是决策\(a_{i+1},b_j\)会不会影响到以后的最优?注意到此时\(a_{i+1},b_{j-1}\)要么没入集合,要么不是最优,显然\(a_{i+1},b_{j-1}\)要比\(a_{i+1},b_j\)优秀,既然它都不够优秀,何况\(a_{i+1},b_j\)呢。
于是我们只要开一个二元组\(\{i,j\}\),比较关键字为\(a_i+b_j\),全部加入小根堆,便于很快地求出最小值,然后推广到多个序列,我们只要把之前的求出了前n小值作为\(\{a_i\},\{b_i\}\)的合并后的序列,然后与第三个序列进行同样的操作即可,最终时间复杂度\(O(nmlog(n))\)。
仔细思考一下,会发现这道题目是最优性贪心+合并性贪心,而且还涉及到对于决策集合的研究,所以自然也就很困难了。
我感受到了我的弱小。
参考代码:
#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
#define il inline
#define ri register
#define Size 2500
using namespace std;
int a[Size],b[Size],c[Size];
template<class free>
struct small{
free a[Size*2];int n;
il void push(free x){
a[++n]=x;int p(n);
while(p>1)
if(a[p]<a[p>>1])
swap(a[p],a[p>>1]),
p>>=1;
else break;
}
il void pop(){
a[1]=a[n--];int p(1),s(2);
while(s<=n){
if(s<n&&a[s+1]<a[s])++s;
if(a[s]<a[p])
swap(a[s],a[p]),p=s,s=p<<1;
else break;
}
}
};
struct er{
int i,j;
il bool operator<(const er&x)const{
return a[i]+b[j]<a[x.i]+b[x.j];
}
};int n;
small<er>T;il void merge();
int main(){int lsy;scanf("%d",&lsy);
while(lsy--){
int m;scanf("%d%d",&m,&n);
for(int i(1);i<=n;++i)scanf("%d",&b[i]);
sort(b+1,b+n+1);
for(int i(1),j;i<m;++i){
for(j=1;j<=n;++j)scanf("%d",&a[j]);
sort(a+1,a+n+1),merge();
for(j=1;j<=n;++j)b[j]=c[j];
}for(int i(1);i<=n;++i)
printf("%d ",b[i]);
putchar('\n');
}
return 0;
}
il void merge(){
T.n=0,T.push({1,1});
for(int i(1),x,y;i<=n;++i){
x=T.a[1].i,y=T.a[1].j;
if(y<n)T.push({x,y+1});
if(y==1&&x<n)T.push({x+1,y});
c[i]=a[x]+b[y],T.pop();
}
}