poj2442 Sequence(堆)

题意

给定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
#include
#include
#include
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 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

 

你可能感兴趣的:(刷题之路,堆)