洛谷 P5369 [PKUSC2018]最大前缀和【动态规划】【状态压缩】

85 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目链接:洛谷 P5369

题目大意:给定长度为 nn 的序列,将序列随机打乱后,求最大前缀和。求结果的期望值。

题目分析:

首先,我们考虑一下答案应该如何进行计算:

maximax_i表示第ii种排列的最大前缀和,由于一共有n!n!个排列,所以最后的答案就应该为:

Ans=(max1n!+max2n!+...+maxn!n!)n!Ans=(\frac{max_1}{n!}+\frac{max_2}{n!}+...+\frac{max_{n!}}{n!})*n!

Ans=i=1n!maxin!n!Ans=\frac{\sum_{i=1}^{n!}max_i}{n!}*{n!}

Ans=i=1n!maxiAns=\sum_{i=1}^{n!}max_{i}

通过上面式子的化简,我们发现最终所求的结果其实就是所有排列的最大前缀和之和。

接下来,我们开始考虑对于每一个排列,最大前缀和出现的位置在何处。我们假设最大前缀和的前缀的最后一个数的位置为pp,那么一定位置pp之后的数列的前缀之和都要<0<0,否则,我们显然可以替换掉位置pp使得最大前缀和达到更大的一个值。所以我们就把每一个排列分为两个部分来考虑,分割点就是pp

我们记Sum[i]Sum[i]表示ii这个状态(排列)的数值之和。

f[i]f[i]表示在ii这个状态下最大前缀和为Sum[i]Sum[i]的方案数,那么显然有转移:

jiSum[i]>0f[i]f[i+j]j∉i,Sum[i]>0,f[i]\rightarrow f[i+j]

g[i]g[i]表示在ii这个状态下的最大前缀和都0\leq 0的方案数,同样,我们有转移:

jiSum[i+j]<=0g[i]g[i+j]j∉i,Sum[i+j]<=0,g[i]\rightarrow g[i+j]

因此,我们就得到了我们想要的最后的答案,根据加法原理和上述状态定义,可以得到:

Ans=i=0maxStateSum[i]f[i]g[maxStatei]Ans=\sum_{i=0}^{maxState}Sum[i]*f[i]*g[maxState-i]

#include <bits/stdc++.h>

const int mod = 998244353;

#define lowbit(x) (x & (-x))
#define add(X,Y) if (((X) += (Y)) >= mod) (X) -= mod

using namespace std;

int n, t, ans, a[1 << 20], sum[1 << 20];
int f[1 << 20], g[1 << 20];

int main(){
    scanf("%d", &n);
    t = (1 << n) - 1;

    for (int i = 0; i < n; ++i) {
        f[1 << i] = 1;
        scanf("%d", &a[1 << i]);
    }

    for (int i = 0; i <= t; ++i) {
        sum[i] = sum[i ^ lowbit(i)] + a[lowbit(i)];
    }

    for (int i = 0; i <= t; ++i) {
        if (sum[i] > 0) {
            for (int j = 0; j < n; ++j) {
                if (!((i >> j) & 1)) {
                    add(f[i | (1 << j)], f[i]);
                }
            }
        }
    }

    g[0] = 1;
    for (int i = 0; i <= t; ++i) {
        if (sum[i] <= 0) {
            for (int j = 0; j < n; ++j) {
                if ((i >> j) & 1) {
                    add(g[i], g[i ^ (1 << j)]);
                }
            }
        }
    }

    for (int i = 0; i <= t; ++i) {
        add(ans, (1ll * (sum[i] + mod) * f[i] % mod * g[t ^ i] % mod));
    }

    printf("%d\n", ans);
    return 0;
}