洛谷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;
}
posted @ 2022-11-14 17:18  SxtoxA  阅读(50)  评论(0)    收藏  举报
12 13