cf 1208E

给定n个序列,每个一行,每个序列是可以左右移动的,对于每行可以随意移动的情况下,求每列的和的最大值

n行(1<=n<=10^6) ,w列(1<=w<=10^6)

题解:

首先由于左右边界的限制,最大为w,所以对于每行,可以填进一个列中的数是被限制的。所以我们要找到在这些可选的数中的最大值。因为这些可选的数都是连续的,并且要找最大值,可以想到用ST表查找区间最大值。

对于每个序列分成两种情况:

1.序列长度小于w的一半,这种情况是有某些处于中间的列可以选择所有输入序列的数,开头/结尾的k个直接维护前k个的最大值。影响进行差分处理。

2.长度大于等于w的一半,直接st表求出最大值即可,使用差分维护。

#include <bits/stdc++.h>
using namespace std;
#define  ll long long
int n,w,m,lg[1000100],st[2000100][23];
ll ans[1000100];
inline int query(int l,int r)
{
    int k=lg[r-l+1];
    return max(st[l][k],st[r-(1<<k)+1][k]);
}
int main()
{
    int i,j,k;
    scanf("%d%d",&n,&w);
    for(i=2;i<=1000000;i++)lg[i]=lg[i>>1]+1;
    for(int kk=1;kk<=n;kk++)
    {
        scanf("%d",&m);
        for(i=1;i<=m;i++)scanf("%d",&st[i][0]);
        for(i=1;i<=20;i++)
            for(j=1;j<=m;j++)
                st[j][i]=max(st[j][i-1],st[j+(1<<(i-1))][i-1]);
         if(2*m>=w)//第二种情况
         {
             for(i=1;i<=w;i++)
             {
                 int l=m-w+i,r=i,v=query(max(l,1),min(r,m));
                 if(l<1||r>m)v=max(v,0);//可以有不选的情况(即靠近端点处的特殊情况)
                 ans[i]+=(ll)v;
                 ans[i+1]-=(ll)v;
             }
         } else {
             int v1 = 0, v2 = 0;
             for (i = 1; i <= m; i++)
             {
                 v1 = max(v1, st[i][0]);
                 v2 = max(v2, st[m - i + 1][0]);
                 ans[i] += (ll) v1;
                 ans[i + 1] -= (ll) v1;
                 ans[w - i + 1] += (ll) v2;
                 ans[w - i + 2] -= (ll) v2;
             }
             ans[m + 1] += (ll) v1;
             ans[w - m + 1] -= (ll) v1;
         }
    }
    for(i=1;i<=w;i++)
    {
        ans[i]+=ans[i-1];
        printf("%lld ",ans[i]);
    }
    puts("");
    return 0;
}
原文地址:https://www.cnblogs.com/nuchenghao/p/11425661.html