• LOJ6433 「PKUSC2018」最大前缀和


    LOJ6433 「PKUSC2018」最大前缀和

    题目大意

    题目链接

    给定一个长度为 (n) 的序列 (a),求它在所有 (n!) 种排列方式下的【最大前缀和】之和。即,对所有 (1dots n) 的排列 (p),求:

    [sum_{p} max_{i = 1}^{n}left{sum_{j = 1}^{i} a_{p_{j}} ight} ]

    数据范围:(1leq nleq 20)(sum_{i = 1}^{n} |a_i|leq 10^9)

    本题题解

    约定:对于一个序列,如果前缀和在多个位置都能取到最大值,我们以最靠后的位置为准。这样每个序列的最大前缀和所在位置都是唯一的,考虑枚举这个位置 (i),观察序列需要满足什么条件:

    1. (forall j in[i + 1, n])(sum_{k = i + 1}^{j} a_k < 0),即 (a)([i + 1, n]) 这段后缀的所有前缀和都小于 (0)
    2. (forall j in[1, i - 1])(sum_{k = j + 1}^{i} a_k geq 0),即 (a)([2, i]) 这段子段的所有后缀和都大于等于 (0)

    这两个条件,就是【(i) 是最大前缀和所在位置】的充分必要条件。因为如果存在不符合上述要求的 (j),则 (j) 会取代 (i) 成为最大前缀和所在位置;反之,则没有 (j) 能成为最大前缀和所在位置。

    另外,请注意,在满足上述两个条件的前提下,我们对 (a_1)没有限制的,它可正,可负,可零。

    状压 DP。

    • (f(s)) 表示:一个序列,用了集合 (s) 里的这些数,且所有后缀和都 (geq 0),这样的序列有多少个。转移时,考虑在序列前面加入一个数即可。
    • (g(s)) 表示:一个序列,用了集合 (s) 里的这些数,且所有前缀和都 (< 0),这样的序列有多少个。转移时,考虑在序列后面加入一个数即可。

    请注意,在 (f(s))(g(s)) 里,我们认为总共有 (|s|!) 种序列,也就是两个相同的数值交换位置后被认为是不同的序列。

    这两个 DP 的时间复杂度都是 (mathcal{O}(2^n n))

    完成 DP 后,考虑统计答案。枚举最终序列里 (a_1) 的值(前文说过,我们对 (a_1) 没有限制)。然后枚举一个不包含 (a_1) 的集合 (s),表示 ([2, i]) 这个子段所用的数。设剩下的 (n - 1 - |s|) 个数为集合 (t)。则此时的方案数是:(f(s) imes g(t))。对答案的贡献是:((a_1 + sum_{xin s}x) imes f(s) imes g(t))

    时间复杂度 (mathcal{O}(2^n n))

    参考代码

    // problem: LOJ6433
    #include <bits/stdc++.h>
    using namespace std;
    
    #define mk make_pair
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;
    
    template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
    template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
    
    const int MAXN = 20;
    const int MOD = 998244353;
    
    inline int mod1(int x) { return x < MOD ? x : x - MOD; }
    inline int mod2(int x) { return x < 0 ? x + MOD : x; }
    inline void add(int &x, int y) { x = mod1(x + y); }
    inline void sub(int &x, int y) { x = mod2(x - y); }
    
    int n, a[MAXN + 5];
    ll sum[1 << MAXN];
    int f[1 << MAXN], g[1 << MAXN];
    
    int main() {
    	cin >> n;
    	for (int i = 0; i < n; ++i) {
    		cin >> a[i];
    	}
    	for (int i = 0; i < (1 << n); ++i) {
    		for (int j = 0; j < n; ++j) {
    			if ((i >> j) & 1) {
    				sum[i] += a[j];
    			}
    		}
    	}
    	
    	f[0] = 1; // f: 任意一个前缀都 >= 0 的方案数
    	for (int i = 0; i <= (1 << n) - 2; ++i) {
    		for (int j = 0; j < n; ++j) {
    			if (!((i >> j) & 1) && sum[i] + a[j] >= 0) {
    				add(f[i | (1 << j)], f[i]);
    			}
    		}
    	}
    	
    	g[0] = 1; // g: 任意一个前缀都 < 0 的方案数
    	for (int i = 0; i <= (1 << n) - 2; ++i) {
    		for (int j = 0; j < n; ++j) {
    			if (!((i >> j) & 1) && sum[i] + a[j] < 0) {
    				add(g[i | (1 << j)], g[i]);
    			}
    		}
    	}
    	
    	int ans = 0;
    	for (int i = 0; i < n; ++i) { // a[1]
    		int all = (((1 << n) - 1) ^ (1 << i));
    		for (int j = 0; j < (1 << n); ++j) {
    			if (!((j >> i) & 1) && sum[j] >= 0) {
    				int s = (sum[j] + a[i] + MOD + MOD) % MOD;
    				add(ans, (ll)f[j] * g[all ^ j] % MOD * s % MOD);
    			}
    		}
    	}
    	cout << ans << endl;
    	
    	return 0;
    }
    
  • 相关阅读:
    我们毕业了!!!@全体成员
    华东交通大学编译原理期末试卷
    软件设计师中级下午答题解题策略分析~
    Java实现旅行商最短距离
    基于SSH的医院在线挂号
    基于Java的模拟写字板的设计与实现
    基于java的雷电游戏
    基于Java的飞机大战游戏的设计与实现
    基于Java的超级玛丽游戏的设计与实现
    基于Javaee的影视创作论坛的设计与实现
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/14495522.html
Copyright © 2020-2023  润新知