[LOJ #6433]「PKUSC2018」最大前缀和

LOJ传送门

题目描述

小 C 是一个算法竞赛爱好者,有一天小 C 遇到了一个非常难的问题:求一个序列的最大子段和。

但是小 C 并不会做这个题,于是小 C 决定把序列随机打乱,然后取序列的最大前缀和作为答案。

小 C 是一个非常有自知之明的人,他知道自己的算法完全不对,所以并不关心正确率,他只关心求出的解的期望值,现在请你帮他解决这个问题,由于答案可能非常复杂,所以你只需要输出答案乘上 n ! n! n! 后对 998244353 998244353 998244353 取模的值,显然这是个整数。

注:最大前缀和的定义: ∀ i ∈ [ 1 , n ] \forall i \in [1,n] i[1,n] ∑ j = 1 i \sum_{j=1}^{i} j=1i的最大值。

输入输出格式

输入格式

第一行一个正整数 n n n,表示序列长度。

第二行 n n n 个数,表示原序列 a [ 1.. n ] a[1..n] a[1..n],第 i i i 个数表示 $a[i] $。

输出格式

输出一个非负整数,表示答案。

输入输出样例

输入样例#1
2
-1 2
输出样例#1
3

数据范围与提示

数据范围

对于 10 % 10\% 10%的数据,有 1 ≤ n ≤ 9 1\leq n\leq 9 1n9

对于 40 % 40\% 40%的数据,有 1 ≤ n ≤ 15 1\leq n\leq 15 1n15

另有 10 % 10\% 10%的数据,满足 a a a中最多只有一个负数。

另有 10 % 10\% 10%的数据,满足 ∣ a [ i ] ∣ ≤ 2 |a[i]|\leq 2 a[i]2

对于 100 % 100\% 100%的数据,满足 1 ≤ n ≤ 20 1\leq n\leq 20 1n20 ∑ i = 1 n ∣ a [ i ] ∣ ≤ 1 0 9 \sum_{i=1}^{n}|a[i]|\leq 10^9 i=1na[i]109

解题分析

P K U W C PKUWC PKUWC做的试机题…

直接考虑排列是行不通的, 考虑一个位置 p p p, 我们什么时候会在这个位置取到最大前缀和:对于 ∀ i ∈ [ 1 , p ] \forall i\in [1,p] i[1,p] ∑ i p a [ i ] ≥ 0 \sum_{i}^{p}a[i]\ge 0 ipa[i]0, 对于 ∀ i ∈ [ p + 1 , n ] \forall i\in[p+1,n] i[p+1,n] ∑ i n a [ i ] &lt; 0 \sum_i^{n}a[i]&lt; 0 ina[i]<0。 这样直接分开 d p dp dp, 最后答案是满足要求的前缀和*前缀排列方案数*后缀排列方案数。

代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <cctype>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MOD 998244353
#define ll long long
template <class T>
IN void in(T &x)
{
	static char c; static bool neg;
	x = 0; c = gc;
	for (; !isdigit(c); c = gc)
	if (c == '-') neg = true;
	for (;  isdigit(c); c = gc)
	x = (x << 1) + (x << 3) + c - 48;
	if (neg) neg = false, x = -x;
}
IN void ad(int &ad, R int add) {ad += add; if (ad > MOD) ad -= MOD;}
const int MX = 21;
const int SIZ = 1 << 20;
int n;
int val[MX], f[SIZ], g[SIZ], sum[SIZ];
int main(void)
{
	R int i, j, foo, all;
	in(n); g[0] = 1; all = (1 << n) - 1;
	for (i = 0; i < n; ++i) in(val[i]), f[1 << i] = 1;
	for (i = 1; i <= all; ++i)
	{
		foo = i;
		W (foo)
		{
			j = __builtin_ctz(foo);
			ad(sum[i], val[j]);
			foo ^= (1 << j);
		}
	}
	for (R int i = 1; i <= all; ++i)
	{
		if (sum[i] > 0)
		{
			for (j = 0; j < n; ++j)
			if (!(i & (1 << j))) ad(f[i | (1 << j)], f[i]);
		}
		else
		{
			for (j = 0; j < n; ++j)
			if (i & (1 << j)) ad(g[i], g[i ^ (1 << j)]);
		}
	}
	int ans = 0;
	for (R int i = 1; i <= all; ++i)
	ad(ans, 1ll * f[i] * g[all ^ i] % MOD * ((sum[i] + MOD) % MOD) % MOD);
	printf("%d", ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值