bzoj5369: [Pkusc2018]最大前缀和 状压Dp 计数Dp

bzoj5369: [Pkusc2018]最大前缀和

Description

小C是一个算法竞赛爱好者,有一天小C遇到了一个非常难的问题:求一个序列的最大子段和。
但是小C并不会做这个题,于是小C决定把序列随机打乱,然后取序列的最大前缀和作为答案。
小C是一个非常有自知之明的人,他知道自己的算法完全不对,所以并不关心正确率,他只关心求出的解的期望值,
现在请你帮他解决这个问题,由于答案可能非常复杂,所以你只需要输出答案乘上n!后对998244353取模的值,显然这是个整数。
注:最大前缀和的定义:i∈[1,n],Sigma(aj)的最大值,其中1<=j<=i

Input

第一行一个正整数nnn,表示序列长度。
第二行n个数,表示原序列a[1…n],第i个数表示a[i]。
1≤n≤20,Sigma(|Ai|)<=10^9,其中1<=i<=N

Output

输出一个非负整数,表示答案。

Sample Input

2
-1 2

Sample Output

3

分析

考场不会写系列。
基本思路是状压 s s s,计算 s u m [ s ] sum[s] sum[s]作为最大前缀和的方案,其中 s u m [ s ] sum[s] sum[s]为状态 s s s各个数的和。
首先为了避免重复,如有多解,用下标最小的最大前缀和标记每种方案。
考虑一个前缀和 S [ 1 ⋯ p ] S[1\cdots p] S[1p]作为最大前缀和的等价条件。

  • 序列 2 ⋯ p 2\cdots p 2p不存在小于等于0的后缀和。
  • 序列 p + 1 ⋯ n p+1\cdots n p+1n不存在大于0的前缀和。

必要性:假设存在大于0的前缀和或小于等于0的后缀和,考虑加上/减去那段前缀/后缀和,就一定找到了一个更加优秀的前缀和,矛盾。
充分性:假设一个更优秀的解,如果在 p p p之后,两端前缀和作差必然得到一个序列 p + 1 ⋯ n p+1\cdots n p+1n的大于0的前缀和,在 p p p之前同理,矛盾。
这两个条件的好处是他们将一个最优性问题转化成了判定性问题。这样提供了一个比较简便的转移思路。
同时发现这两个条件是独立的,前一个条件与后一个条件是两个独立的问题,于是我们可以把目标集合划分成两个部分,前一个部分满足条件1,将其作为前缀,后一个部分满足条件2,将其作为后缀,乘法原理即可计算方案数。
具体地,设 f [ s ] f[s] f[s]表示集合 s s s为一个合法的满足条件1的前缀的方案数。枚举一个新的数 x x x,如果这个数可以合法地放在 s s s最前,必有 s u m [ s ] &gt; 0 sum[s]&gt; 0 sum[s]>0(满足条件1),类似地,设 g [ s ] g[s] g[s]表示集合 s s s为一个合法的满足条件2的后缀的方案数,枚举一个新的数 x x x,如果这个数可以合法地放在 s s s最后,则必有 s u m [ s ] + a [ x ] ≤ 0 sum[s]+a[x]\le 0 sum[s]+a[x]0
最终的答案即为 ∑ f [ s ] ⋅ g [ a l l − s ] \sum f[s]\cdot g[all - s] f[s]g[alls]

代码

#include<bits/stdc++.h>
const int P = 998244353, S = 1048576;
int ri() {
    char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
    for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int f[S], g[S], sum[S], bn[21], n;
void Up(int &a, int b) {a += b; a %= P;}
int main() {
    g[0] = bn[0] = 1; for(int i = 1;i <= 20; ++i) bn[i] = bn[i - 1] << 1;
    n = ri();
    for(int i = 0;i < n; ++i) {
        int x = ri(); f[bn[i]] = 1;
        for(int s = 0;s < bn[n]; ++s) 
            if(s & bn[i]) sum[s] += x;
    }
    for(int s = 0;s < bn[n]; ++s) {
        if(sum[s] > 0) {
            for(int i = 0;i < n; ++i) 
                if(~s & bn[i]) Up(f[s | bn[i]], f[s]);
        }
        else {
            for(int i = 0;i < n; ++i)
                if(s & bn[i]) Up(g[s], g[s ^ bn[i]]);
        }
    }
    int r = 0;
    for(int s = 0;s < bn[n]; ++s) Up(r, 1LL * sum[s] * f[s] % P * g[bn[n] - 1 ^ s] % P);
    printf("%d\n", (r + P) % P);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值