LG5369 [PKUSC2018] 最大前缀和

Problem

LG5369 [PKUSC2018] 最大前缀和

Analysis

正解思路

题目强调了最大前缀和,那么我就应该思考最大前缀和的性质:
设当前序列为 \(a\),设前 \(i\) 个为最大前缀。

  1. 不存在 \(1< j\le i\) 使得 \(\sum_{k=j}^{i}a_k<0\),否则可以直接舍去。(注意这里的最大前缀和必须选择数,所以 \(j\) 不能从 \(1\) 开始)
  2. 不存在 \(i< j\le n\) 使得 \(\sum_{k=i+1}^{j}a_k\ge 0\),否则可以直接加上。

其实我们也就发现这是前 \(i\) 个取到最大前缀和的充要条件。 (对于性质的刻画最终都需要回到这一步)

再根据 \(n\le 20\) 很小,我们就容易想到状压 dp 解决,这样就首先明确了计算方法。

考虑解决问题,答案其实是解与方案数的乘积。解是很好计算的,枚举子集统计和就行,记为 \(sum_i\)。考虑方案数的计算,发现这两个性质条件互不冲突,那么可以分别计算它们的方案数,再相乘就得出答案。

\(U\) 为全集,设 \(f_i\) 为集合 \(i\) 组成的排列最大前缀和 \(=sum_i\) 的方案数,\(g_i\) 为集合 \(i\) 组成的排列最大前缀和 \(<0\) 的方案数。分别对应条件 \(1\)\(2\)

考虑转移,\(g\) 的转移很直接,若 \(sum_i\ge 0\)\(g_i=0\),否则,枚举当前加入的数放在最后一位,\(g_i=\sum_{j\in i}g_{i-j}\)\(f\) 的转移由于无法确定最终序列最大前缀和(总和)是否 \(\ge 0\),所以只能往后刷表,若 \(sum_i\ge 0\)\(f_{i+j}=\sum_{j\notin i}f_i\)

错因总结

我花费了很多时间从排列的性质考虑问题,这确实是一个方向,但没有尝试思考最大前缀和的性质是一个很严重的思维局限问题。

AC Code

点击查看代码
#include <bits/stdc++.h>
#define ll long long 
#define ull unsigned long long 
#define i128 __int128
#define fi first
#define se second
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define mk make_pair
#define INF 0x3f3f3f3f
#define INFx 0x3f3f3f3f3f3f3f3f
using namespace std;

const int N = 21, M = 1 << N;
const int mod = 998244353;

int n;
int a[N];
int s[M], f[M], g[M];

void add(int &x, int y) {
    x += y;
    if (x >= mod) x -= mod;
} 

int main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);

    cin >> n;
    
    for (int i = 1; i <= n; i ++) cin >> a[i];

    for (int i = 0; i < (1 << n); i ++) {
        for (int j = 1; j <= n; j ++) {
            if (i >> j - 1 & 1) add(s[i], a[j]);
        }
    }

    for (int i = 1; i <= n; i ++) f[1 << i - 1] = 1; // 注意单个元素也算
    g[0] = 1;
    for (int i = 1; i < (1 << n); i ++) {
        if (s[i] >= 0) {
            for (int j = 1; j <= n; j ++) {
                if (!(i >> j - 1 & 1)) add(f[i ^ (1 << j - 1)], f[i]); 
            }
        }
        else {
            for (int j = 1; j <= n; j ++) {
                if (i >> j - 1 & 1) add(g[i], g[i ^ (1 << j - 1)]);
            }
        }
    }

    int ans = 0, tot = (1 << n) - 1;
    for (int i = 0; i < (1 << n); i ++) {
        s[i] = (s[i] + mod) % mod;
        add(ans, 1ll * s[i] * f[i] % mod * g[tot ^ i] % mod);
    }
    
    cout << ans << '\n';

    return 0;
}
posted @ 2026-02-27 12:08  KenopsiaMind  阅读(10)  评论(0)    收藏  举报