uva11997 - K Smallest Sums

本文详细解析了UVA-11997问题的解决方案,采用巧妙的方法将多序列最小和问题转化为两序列问题,通过优先级队列实现高效求解。介绍了刘汝佳书中提及的k路归并技巧,适用于处理k个有序序列的前n项求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链接

https://vjudge.net/problem/UVA-11997

题解

真是涨姿势了
首先,我可以把 k k k个序列的问题拆成多个 2 2 2个序列的问题来做
我可以先求前两个序列和的前 k k k小,然后把这 k k k个数当成一个新的序列,再和第三个序列这样来做
如此做的科学性在哪里呢?
假设我最后的答案表示成若干个三元组 ( a 1 , b 1 , c 1 ) , ( a 2 , b 2 , c 2 ) . . . ( a k , b n , c k ) (a_1,b_1,c_1),(a_2,b_2,c_2)...(a_k,b_n,c_k) (a1,b1,c1),(a2,b2,c2)...(ak,bn,ck)
显然根据我的方法,任何 ( a i , b i , c i ) (a_i,b_i,c_i) (ai,bi,ci)中的 a i + b i a_i+b_i ai+bi都属于前两个序列得到的前 k k k小,如果我把它替换成一个 a i + b i a_i+b_i ai+bi不是前 k k k小的,答案就会变得更劣
也就是说原来的答案是最优的
如此归纳下去就可以证明以上做法的正确性

然后说一下怎么处理 k = 2 k=2 k=2的问题
直接求出 k 2 k^2 k2个和是不现实的
刘汝佳的书中给出了这样的神奇思路:
假设序列是 { A k } \{A_k\} {Ak} { B k } \{B_k\} {Bk},我先使 B i B_i Bi有序
然后看作 k k k路归并
A 1 + B 1 ≤ A 1 + B 2 ≤ ⋯ ≤ A 1 + B k A_1+B_1 \leq A_1+B_2\leq \dots \leq A_1+B_k A1+B1A1+B2A1+Bk
… \dots
A k + B 1 ≤ A k + B 2 ≤ ⋯ ≤ A k + B k A_k+B_1 \leq A_k+B_2\leq \dots \leq A_k+B_k Ak+B1Ak+B2Ak+Bk
现在就成了求这 k k k个有序表归并得到的最终表的前 k k k
可以想到先把每个表的表头存到优先级队列里去,然后取 k k k次,每次把后继点放进去
这种做法是基于有序性的,因为当 A i + B j A_i+B_j Ai+Bj还在优先队列里的时候, A i + B j + 1 A_i+B_{j+1} Ai+Bj+1目前就不会影响到答案
其实只要是 k k k个有序序列,满足不减,而且由每一项都能推出下一项,让求最终归并得到的表的前 n n n个数的时候,就可以用这种方法
可以想象,这种有序序列不一定是这个题给出的这种,也可以是一个有递推式的数列(此处可出题)

代码

#include <bits/stdc++.h>
#define maxn 1010
#define linf (1ll<<60)
#define cl(x) memset(x,0,sizeof(x))
#define mkp(x,y) make_pair((x),(y))
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pr;
ll a[maxn], N, last[maxn];
void merge(ll *a, ll *b, ll *c)
{
    ll i;
    priority_queue< pr, vector<pr>, greater<pr> > pq;
    for(i=1;i<=N;i++)
    {
        pq.emplace(mkp(a[i]+b[1],1));
    }
    for(i=1;i<=N;i++)
    {
        auto x=pq.top(); pq.pop();
        c[i]=x.first;
        if(x.second<N)pq.emplace(mkp(x.first-b[x.second]+b[x.second+1],x.second+1));
    }
}
int main()
{
    ll i, j;
    while(cin>>N)
    {
        for(i=1;i<=N;i++)
        {
            for(j=1;j<=N;j++)scanf("%lld",a+j);
            sort(a+1,a+N+1);
            if(i==1)
            {
                memcpy(last,a,sizeof(last));
                continue;
            }
            merge(last,a,last);
        }
        for(i=1;i<=N;i++)printf("%lld",last[i]), putchar(i==N?10:32);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值