bzoj5369 [Pkusc2018]最大前缀和

题目描述:

bz

luogu

题解:

不带组合数玩的计数问题。

先只考虑最大前缀和大于$0$的情况。

首先对于一个数列,他的最大前缀和有一个性质,即(高能预警):

(分割线的意思是满足该线左边为最大前缀的,最靠左的分割线)

分割线左边的数列,任意后缀都必须$>0$;

分割线右边的数列,任意前缀都必须$leq 0$。

这样满足最大。

所以状压一波,转移可以按照在当前状态后边(或者前边)再接一个数来转移。

然后我脑残一交发现只有$55$。

后来注意到前缀必须从$1$开始,就发现大事不妙。

其实也很简单。当$sum  = sum a_i < 0$时会出现负的最大前缀。

怎么算呢?

这个最大前缀和还有一个性质:

分割线左边的每一个后缀(除了整个前缀)都$>0$;

分割线右边的每一个前缀都$leq 0$。

所以左边第一位(即开头)一定是负数,然后左边第二位到分割线和分割线右边的状态数我们已经算完了。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 22;
const int M = (1<<20)+50;
const int MOD = 998244353;
template<typename T>
inline void read(T&x)
{
    T f = 1,c = 0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){c=c*10+ch-'0';ch=getchar();}
    x = f*c;
}
int n;
ll a[N],ans;
void Mod(ll&x){if(x>=MOD)x-=MOD;}
ll cot[M],_id[M];
ll c0[M],c1[M];//<= >
int main()
{
//    freopen("tt.in","r",stdin);
    read(n);int MX = (1<<n)-1;
    ll sum = 0;
    for(int i=0;i<n;i++)read(a[i]),_id[1<<i]=i,sum+=a[i];
    for(int i=1;i<(1<<n);i++)cot[i]=cot[i^(i&-i)]+a[_id[i&-i]];
    c0[0]=c1[0]=1;
    for(int i=0;i<(1<<n);i++)for(int j=0;j<n;j++)if(!(i&(1<<j)))
    {
        if(cot[i]+a[j]<=0)Mod(c0[i|(1<<j)]+=c0[i]);
        else Mod(c1[i|(1<<j)]+=c1[i]);
    }
    for(int i=0;i<(1<<n);i++)Mod(ans+=c0[i]*c1[MX^i]%MOD*cot[MX^i]%MOD);
    if(sum<0)
    {
        for(int i=0;i<(1<<n);i++)if(!i||cot[i]>0)
            for(int j=0;j<n;j++)if(!(i&(1<<j))&&cot[i]+a[j]<0)
                Mod(ans+=c1[i]*c0[MX^(i|(1<<j))]%MOD*(MOD+cot[i]+a[j])%MOD);
    }
    printf("%lld
",ans);
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/LiGuanlin1124/p/10872864.html