洛谷P8848 [JRKSJ R5] 1-1 B 组合+dp
题面
https://www.luogu.com.cn/problem/P8848
分析
比赛时打了个暴力就跑了,事后看讨论帖分析出来了正解。
1-1A的结论就是,最大子段和一定是 \(\sum a_i\)。因为最优数组排列一定是1与-1交叉排列。
首先容易发现,如果数组中\(-1\)的个数比\(1\)出现的多(或者相等),那么无论有多少\(-1\),对最大子段和是没有贡献的。
设\(-1\)的个数为\(tot0\) , \(1\)的个数为\(tot1\)。这种情况下,问题转化为在tot0个数之间插tot1个板,很显然的插板法,答案为\(C_{tot0+1}^{tot1}\)。
对于第二种情况,即\(-1\)的个数比\(1\)出现的少,考虑动态规划。
令\(dp[i][j]\)为前\(i\)个数且和为\(j\)的方案数。易得转移方程为:
\[dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1]
\]
答案即为\(dp[n][\sum a_i]\)。
但是\(1e4\)的数据会被卡\(MLE\)(比如我),考虑滚动数组优化,代码参考@此处
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
const int maxn = 1e4 + 10;
const int mod = 998244353;
int n, a[maxn], sum;
ll dp[maxn], dp2[maxn];
ll fac[maxn];
int tot, tot2;
ll qpow(ll a, ll b) {
ll ans = 1LL, base = a;
while (b) {
if (b & 1) ans = (ans * base) % mod;
base = (base * base) % mod;
b >>= 1;
}
return ans;
}
ll C(ll n, ll k) {
if (k > n) return 0;
return (fac[n] * qpow(fac[k], mod - 2) % mod) * qpow(fac[n - k], mod - 2) % mod;
}
ll Lucas(ll n, ll k) {
if (!k) return 1LL;
return C(n % mod, k % mod) * Lucas(n / mod, k / mod) % mod;
}
int main() {
fac[0] = 1LL;
scanf("%d", &n);
for (int i = 1; i <= n + 1; i++) fac[i] = (fac[i - 1] * i) % mod;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
if (a[i] == -1) tot++;
else tot2++;
sum += a[i];
}
if (tot >= tot2) {
printf("%lld", Lucas(tot + 1, tot2));
} else {
dp2[0] = 1;
for (int i = 1; i <= n; i++) {
dp[0] = dp2[1];
for (int j = 1; j <= sum; j++)
dp[j] = (dp2[j - 1] + dp2[j + 1]) % mod;
for (int j = 0; j <= sum; j++)
dp2[j] = dp[j];
}
printf("%lld", dp[sum]);
}
return 0;
}