poj2442 Sequence(堆)

版权声明:本文为博主原创文章,未经博主允许不得转载,除非先点了赞。 https://blog.csdn.net/A_Bright_CH/article/details/81674160

题意

给定m个长度为n的序列,从每个序列中任取一个数求和,可以构成n^m个和,求其中最小的n个和。

题解


这题就像同余方程求解一样,可以分步求解。对于第i行和第i+1行的解是一个1*n的矩阵,我们将其替代第i行和第i+1行,放回原矩阵中。
这样一来问题就简化成2个长度为n的序列,分别任取一个数求和,求最小的n个。

这两个序列a,b内部先分别有小到大的排序。最小解无疑是(a[1],b[1])。次小解可能是(a[2],b[1])或(a[1],b[2])。容易想到,邻近(a[i],b[j])的是(a[i+1],b[j])和(a[i],b[j+1]),这两者都有可能成为最小解。
但是有一个重复的问题,我们从决策树上可以看出来,红色部分是已出现状态。怎么去重呢?我们想到一个锁定的方法。一个决策本来有两种选择:(a[i+1],b[j])和(a[i],b[j+1]);如果一旦选择了(a[i],b[j+1]),那它后面只能选择这项了,也就是说只能一直往右走。一开始我很奇怪,会不会(a[2],b[3])是前n小的,但是因为(a[1],b[3])不能往左走导致这个状态没有出现呢?其实是想多了。如果(a[2],b[3])是前n小的,那么(a[2],b[2])一定也是前n小的,只要(a[2],b[2])出现了,(a[2],b[3])也会成为备选状态。这样处理是正确而简便的。
把所有解连同(x,y,last)组成三元组放入小根堆,每次找到最小的一个。重复n次即可完成。复杂度O(m*n*logn)。

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxm=110,maxn=2010;

int m,n;
int s[3][maxn];
struct N{int x,y,c;bool last;};//last=true有两种决策可选,last=false只能往右走
bool operator <(N n1,N n2)
{
    return n1.c>n2.c;
}

int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&m,&n);
        int a,b,c=0;
        for(int i=1;i<=n;i++) scanf("%d",&s[c][i]);
        sort(s[c]+1,s[c]+n+1);
        for(int i=2;i<=m;i++)
        {
            a=c;b=(a+1)%3;c=(a+2)%3;
            for(int j=1;j<=n;j++) scanf("%d",&s[b][j]);
            sort(s[b]+1,s[b]+n+1);
            priority_queue<N> q;
            q.push((N){1,1,s[a][1]+s[b][1],true});
            for(int j=1;j<=n;j++)
            {
                N top=q.top();q.pop();
                s[c][j]=top.c;//s[a][top.x]+s[b][top.y];
                if(top.y<n) q.push((N){top.x, top.y+1, s[a][top.x]+s[b][top.y+1], false});
                if(top.x<n&&top.last) q.push((N){top.x+1, top.y, s[a][top.x+1]+s[b][top.y], true});
            }
        }
        for(int i=1;i<n;i++) printf("%d ",s[c][i]);
        printf("%d\n",s[c][n]);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/A_Bright_CH/article/details/81674160