UVA11997 K Smallest Sums 题解

博客观赏效果更佳

题意简述

一个大小为 n n 的方阵,每行都选一个数,得到一个和。一共能得到 n n n^n 个和。求出最小的前 n n 个。重复的算多次。 n < = 700 n<=700

思路框架

每两行都来一个多路归并即珂。

具体思路

今天去看了刘汝佳的小蓝书。感谢刘汝佳,让我知道优先队列原来还能这么用。

首先,每行里面顺序不重要,先排序再说。

前置知识:n路归并问题

我们应该都知道“二路归并”问题:两个有序的序列,要合并成一个有序的序列。解法很简单,维护两个指针,哪个小就把哪个加入答案,然后让指针++。利用它,我们珂以写出大名鼎鼎的「归并排序」算法。

n n 路归并问题就是: n n 个序列,每个序列都是有序的。要合并成一个长度为 n 2 n^2 的有序的序列。

但是我们发现,我们要维护 n n 个指针,一次维护指针是 O ( n ) O(n) 的,我们一共有 n 2 n^2 的元素,就变成 O ( n 3 ) O(n^3) 了。怎么样更快呢?我们珂以用优先队列来找到值最小的那个指针。这样维护一次指针是 O ( l o g n ) O(logn) 的,总共就是 O ( n 2 l o g n ) O(n^2logn) 的了。而且原问题中我们只需要求前面的 n n 个,所以这会更快,变成每次 O ( n l o g n ) O(nlogn) 的时间。

如何转化

我们的原问题是有 n n n n n^n 个和的,如何转化?

首先我们珂以每两行之间逐个合并。然后对于两行 A A B B 的问题,我们只需要生成这样 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 [ 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 个: 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 1 n-1 次,每次都只求前面 n n 个小的,我们就珂以求出 n n n^n 个和中最小的 n n 个了。

实现细节:关于如何维护指针

如果您足够强,您完全不用看这个。

我们维护两个值, s , b s,b s s 表示目前的 A [ a ] + B [ b ] A[a]+B[b] b b 表示在 B B 中的下标。我们完全不用知道 A A 中的下标,因为 A [ a ] A[a] 是不会改变的。我们有 ( 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 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;
}
发布了210 篇原创文章 · 获赞 8 · 访问量 8970

猜你喜欢

转载自blog.csdn.net/LightningUZ/article/details/103843639