题意简述
一个大小为 n n n的方阵,每行都选一个数,得到一个和。一共能得到 n n n^n nn个和。求出最小的前 n n n个。重复的算多次。 n < = 700 n<=700 n<=700。
思路框架
每两行都来一个多路归并即珂。
具体思路
今天去看了刘汝佳的小蓝书。感谢刘汝佳,让我知道优先队列原来还能这么用。
首先,每行里面顺序不重要,先排序再说。
前置知识:n路归并问题
我们应该都知道“二路归并”问题:两个有序的序列,要合并成一个有序的序列。解法很简单,维护两个指针,哪个小就把哪个加入答案,然后让指针++。利用它,我们珂以写出大名鼎鼎的「归并排序」算法。
n n n路归并问题就是: n n n个序列,每个序列都是有序的。要合并成一个长度为 n 2 n^2 n2的有序的序列。
但是我们发现,我们要维护 n n n个指针,一次维护指针是 O ( n ) O(n) O(n)的,我们一共有 n 2 n^2 n2的元素,就变成 O ( n 3 ) O(n^3) O(n3)了。怎么样更快呢?我们珂以用优先队列来找到值最小的那个指针。这样维护一次指针是 O ( l o g n ) O(logn) O(logn)的,总共就是 O ( n 2 l o g n ) O(n^2logn) O(n2logn)的了。而且原问题中我们只需要求前面的 n n n个,所以这会更快,变成每次 O ( n l o g n ) O(nlogn) O(nlogn)的时间。
如何转化
我们的原问题是有 n n n行 n n n^n nn个和的,如何转化?
首先我们珂以每两行之间逐个合并。然后对于两行
A
A
A和
B
B
B的问题,我们只需要生成这样
n
n
n个序列即珂:
第一个:
A
[
1
]
+
B
[
1
]
,
A
[
1
]
+
B
[
2
]
,
.
.
.
A
[
1
]
+
B
[
n
]
A[1]+B[1],A[1]+B[2],...A[1]+B[n]
A[1]+B[1],A[1]+B[2],...A[1]+B[n]。
第二个:
A
[
2
]
+
B
[
1
]
,
A
[
2
]
+
B
[
2
]
,
.
.
.
A
[
2
]
+
B
[
n
]
A[2]+B[1],A[2]+B[2],...A[2]+B[n]
A[2]+B[1],A[2]+B[2],...A[2]+B[n]。
…
第
n
n
n个:
A
[
n
]
+
B
[
1
]
,
A
[
n
]
+
B
[
2
]
.
.
.
A
[
n
]
+
B
[
n
]
A[n]+B[1],A[n]+B[2]...A[n]+B[n]
A[n]+B[1],A[n]+B[2]...A[n]+B[n]。
然后跑一个
n
n
n路归并即珂。这样做
n
−
1
n-1
n−1次,每次都只求前面
n
n
n个小的,我们就珂以求出
n
n
n^n
nn个和中最小的
n
n
n个了。
实现细节:关于如何维护指针
如果您足够强,您完全不用看这个。
我们维护两个值, s , b s,b s,b。 s s s表示目前的 A [ a ] + B [ b ] A[a]+B[b] A[a]+B[b], b b b表示在 B B B中的下标。我们完全不用知道 A A A中的下标,因为 A [ a ] A[a] A[a]是不会改变的。我们有 ( s , b ) (s,b) (s,b),下一个指针 ( s ′ , b ′ ) (s',b') (s′,b′)其实就是 ( s − B [ b ] + B [ b + 1 ] , b + 1 ) (s-B[b]+B[b+1],b+1) (s−B[b]+B[b+1],b+1)。
在优先队列中,我们显然是要以 s s s为关键字判断优先级的。
—— 刘汝佳的思路
代码
#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
#define N 822
#define F(i,l,r) for(int i=l;i<=r;++i)
#define D(i,r,l) for(int i=r;i>=l;--i)
#define Fs(i,l,r,c) for(int i=l;i<=r;c)
#define Ds(i,r,l,c) for(int i=r;i>=l;c)
#define MEM(x,a) memset(x,a,sizeof(x))
#define FK(x) MEM(x,0)
#define Tra(i,u) for(int i=G.Start(u),__v=G.To(i);~i;i=G.Next(i),__v=G.To(i))
#define p_b push_back
#define sz(a) ((int)a.size())
#define iter(a,p) (a.begin()+p)
void R1(int &x)
{
x=0;char c=getchar();int f=1;
while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=(f==1)?x:-x;
}
int n;
int a[N][N];
void Input()
{
F(i,1,n) F(j,1,n) R1(a[i][j]);
F(i,1,n) sort(a[i]+1,a[i]+n+1);
}
struct node{int s,b;};bool operator<(node a,node b){return a.s>b.s;}
//用一个struct维护指针,排序条件是以s为关键字的
priority_queue<node> Q;
void Merge(int a[N],int b[N],int c[N]) //A和B序列中生成的n^2个和中,排序之后前面n个存在C数组中
//Merge: O(nlogn)/次
{
while(!Q.empty()) Q.pop(); //勿忘清空啊
F(i,1,n) Q.push((node){a[i]+b[1],1}); //队列的初始状态
F(i,1,n) //只求前面n个,i循环到n即珂
{
node Min=Q.top();Q.pop();
c[i]=Min.s;
Q.push((node){Min.s-b[Min.b]+b[Min.b+1],Min.b+1}); //求出下一个指针
}
}
void Soviet()
{
F(i,2,n)
{
Merge(a[1],a[i],a[1]); //不断合并,合并的结果都放在a[1]里面
} //总共O(n^2logn)
F(i,1,n) printf("%d%c",a[1][i]," \n"[i==n]);
}
#define Flan void
Flan IsMyWife()
{
while(~scanf("%d",&n) and n)
{
Input();
Soviet();
}
}
}
int main()
{
Flandre_Scarlet::IsMyWife();
getchar();getchar();
return 0;
}