Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
题目链接:洛谷 P5369
题目大意:给定长度为 的序列,将序列随机打乱后,求最大前缀和。求结果的期望值。
题目分析:
首先,我们考虑一下答案应该如何进行计算:
设表示第种排列的最大前缀和,由于一共有个排列,所以最后的答案就应该为:
通过上面式子的化简,我们发现最终所求的结果其实就是所有排列的最大前缀和之和。
接下来,我们开始考虑对于每一个排列,最大前缀和出现的位置在何处。我们假设最大前缀和的前缀的最后一个数的位置为,那么一定位置之后的数列的前缀之和都要,否则,我们显然可以替换掉位置使得最大前缀和达到更大的一个值。所以我们就把每一个排列分为两个部分来考虑,分割点就是。
我们记表示这个状态(排列)的数值之和。
表示在这个状态下最大前缀和为的方案数,那么显然有转移:
表示在这个状态下的最大前缀和都的方案数,同样,我们有转移:
因此,我们就得到了我们想要的最后的答案,根据加法原理和上述状态定义,可以得到:
#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;
}