LOJ #6433. 「PKUSC2018」最大前缀和(状压dp)

本文解析了一道关于最大前缀和的算法题目,通过深入分析得出了解决方案。利用两个辅助数组f和g分别记录满足特定条件的方案数量,最终通过枚举计算得出答案。

题面

LOJ #6433. 「PKUSC2018」最大前缀和

题解

这题有一定思维难度 ... (我就没想出来 , 靠大佬口胡的)

不难发现 , 成为最大前缀和位置 \(p\) 后面的所有前缀都不能 \(> 0\) .

如果可以 \(>0\) 那么后面必存在一点可以替换当前的答案 .

有了这个思路 , 那我们可以把每个序列拆成两端考虑 , 而分割点就是位置 \(p\) .

首先令 \(sum_i\)\(i\) 这个状态所有点的代数和 , 便于转移 .

  1. \(f_{i}\)\(i\) 这个状态 满足 集合的最大前缀和 等于 集合和 \(sum_i\) 的方案数 .

    这个转移的话 , 我们考虑倒着插入数字 , 如果存在后缀这个 \(sum_{suf} >0\) 那么可以直接转移了 .

    因为这样构造 , 新得到的序列肯定比当前的更加优秀 . 转移方程就是

    \[\displaystyle k \notin i ~\mathrm{and}~ sum_i > 0 : f_i \to f_{i+k}\]

  2. \(g_i\)\(i\) 这个状态 满足 所有前缀和\(\le 0\) 的方案数 .

    这个容易转移一些 , 从前往后插入 , 每次填入的集合和都需要 \(\le 0\) 就行了.

    \[k \notin i ~\mathrm{and}~ sum_{i+k} \le 0 : g_i \to g_{i+k}\]

有了这个答案就是 \(\sum sum_{i} \times f_i \times g_{\mathrm{maxsta}-i}\) .

总时间复杂度就是 \(O(n \times 2^n)\) 然后我用 \(O(2^n)\) 求的 \(sum\) 数组 , 卡进了rank1 ...qwq

代码

#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
using namespace std;

inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}

inline int read() {
    int x = 0, fh = 1; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
    for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
    return x * fh;
}

void File() {
#ifdef zjp_shadow
    freopen ("6433.in", "r", stdin);
    freopen ("6433.out", "w", stdout);
#endif
}

const int N = 21, inf = 0x7f7f7f7f, Mod = 998244353;
int n, a[N], sum[1 << N], f[1 << N], g[1 << N], num[1 << N];

#define lowbit(x) (x & -x)
#define Add(a, b) if (((a) += (b)) >= Mod) (a) -= Mod

int main () {
    File();

    n = read();
    For (i, 0, n - 1) num[1 << i] = a[i] = read();

    int maxsta = (1 << n) - 1;
    For (i, 0, maxsta)
        sum[i] = sum[i ^ lowbit(i)] + num[lowbit(i)];

    g[0] = 1; 
    For (i, 0, maxsta) if (sum[i] <= 0) 
        For (j, 0, n - 1) if ((i >> j) & 1)
            Add(g[i], g[i ^ (1 << j)]);
    
    For (i, 0, n - 1) f[1 << i] = 1;

    int ans = 0;
    For (i, 0, maxsta) {
        if (sum[i] > 0)
            For (j, 0, n - 1) if (!((i >> j) & 1))
                Add(f[i | (1 << j)], f[i]);
        Add(ans, (1ll * (sum[i] + Mod) * f[i] % Mod * g[maxsta ^ i] % Mod));
    }
            
    printf ("%d\n", ans);

    return 0;
}

转载于:https://www.cnblogs.com/zjp-shadow/p/9141971.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值