CF1024E Natasha, Sasha and the Prefix Sums——DP/数学(组合数)

题面

  CF1024E

解析

  题意就是要求所有由$n$个$1$、$m$个$-1$构成的序列的最大前缀和的和

 算法一$(DP)$

   $n$, $m$都小于等于$2000$, 显然可以$DP$

  设$dp[i][j]$表示由$i$个$1$, $j$个$-1$构成的序列的最大前缀和的和

  $i$个$1$, $j$个$-1$构成的序列, 可以看做是在$i-1$个$1$, $j$个$-1$的序列的最前面加一个$1$得到,也可以看做是在$i$个$1$, $j-1$个$-1$的序列最前面加一个$-1$得到

  这也就意味着,$dp[i][j]$可以由$dp[i-1][j]$与$dp[i][j-1]$转移过来

  先考虑$dp[i-1][j]$对$dp[i][j]$的贡献,对于任意一种$i-1$个$1$, $j$个$-1$的序列, 在其首端加入一个$1$后,最大前缀和都会加$1$, 序列的个数为$C(i+j-1, j)$,因此$dp[i][j] += dp[i-1][j] + 1 * C(i-j+1, j)$

  在考虑$dp[i][j-1]$对$dp[i][j]$的贡献,这个和上面那个不一样,要麻烦一点。因为对于最大前缀和为$0$的序列,在其首端加入一个$-1$后,其最大前缀和仍然是$0$, 所以$dp[i][j] += dp[i][j-1] - 1 * (C(i-j+1, j-1) - f[i][j-1])$, 其中数组$f[i][j]$表示由$i$个$1$, $j$个$-1$构成的所有序列中, 最大前缀和等于$0$的序列个数

  因此问题变成了如何求f数组

  与求$dp$数组的思维过程类似。 $i$个$1$, $j$个$-1$构成的序列, 可以看做是在$i-1$个$1$, $j$个$-1$的序列的最后面(注意这里是后面,而$dp$数组是在前面)加一个$1$得到,也可以看做是在$i$个$1$, $j-1$个$-1$的序列最后面加一个$-1$得到

  对于$f$数组,显然有$i \leqslant j$,那么无论在序列末尾插入$1$或$-1$,  原来最大前缀和为$0$的序列在插入$1$或$-1$后,其最大前缀和依然为$0$,因此$f[i][j] = f[i-1][j] + f[i][j-1]$

  状态转移方程大概就是这样了

  初始化:

  $f[0][i] = 1 (1 \leqslant i \leqslant m)$, 其余为$0$

  $dp[i][0] = i (1 \leqslant i \leqslant n)$, 其余为$0$

  答案就是$dp[n][m]$

  时间复杂度$O(NM)$

  在考试中我也定义出来了$dp$, $f$数组, 但无论我怎么想就是想不出来转移方程, 结果方程也并不复杂, $dp$还得多加练习啊

 代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2004, mod = 998244853;

int n, m;
ll dp[maxn][maxn], f[maxn][maxn], C[maxn<<1][maxn<<1];

void init()
{
    C[0][0] = 1LL;
    for(int i = 1; i <= n + m; ++i)
    {
        C[i][0] = 1LL;
        for(int j = 1; j <= i; ++j)
            C[i][j] = (C[i-1][j-1] + C[i-1][j]) % mod;
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    init();
    for(int i = 1; i <= m; ++i)
    {
        f[0][i] = 1;
        for(int j = 1; j <= i; ++j)
            f[j][i] = (f[j-1][i] + f[j][i-1]) % mod;
    }
    for(int i = 1; i <= n; ++i)
        dp[i][0] = i;
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= m; ++j)
            dp[i][j] = (((dp[i-1][j] + C[i+j-1][j] + dp[i][j-1] - C[i+j-1][i] + f[i][j-1]) % mod) + mod) % mod;
    printf("%lld\n", dp[n][m]);
    return 0;
}

算法二(数学)

   这个算法的思路很巧妙, 是从这篇博客中学来的

  如果在坐标系中把$1$看作是向右走一步, $-1$看作是向上走一步, 起点是$(0, 0)$,  终点是$(n, m)$, 那么任意一个序列就会变成从$(0, 0)$出发, 只能向右或向上走,走到$(n, m)$的一条路径

  设$f[i]$为最大前缀和大于等于i的序列个数

  那么$f[i]$就等于路径中存在一点$(x, y)$满足如下条件的路径数,$x$,$y$使得$i \leqslant x - y$, 即$y \leqslant x - i$, $(1 \leqslant x \leqslant n, 1 \leqslant y \leqslant m)$

  结合线性规划的思想,也就是说$f[i]$等于路径经过直线$y = x - i$ 及其下面区域的路径数

  考虑任意一条路径都会到$(n, m)$,因此当$1 \leqslant i \leqslant n - m$ 时, $f[i] = C(n + m , n)$

  而当$max(n - m, 1) \leqslant i \leqslant n$ 时, 我们需要把路径转化一下,起点不再是$(0, 0)$,而是$(i, -i)$,终点不变, 同样是要走$n + m$步走到$(n, m)$,因为只能向右和向上走,那么就一定会经过直线$y = x - i$ 及其下面区域, 因此此时$f[i] = C(n + m, n - i)$

  显然除了上述两种情况外的i, 都有$f[i] = 0$

  那么最终的答案为$\sum_{i = 1}^{n} i * (f[i] - f[i+1])$

  预处理阶乘与阶乘逆元,以便快速求出组合数

  时间复杂度为$O(N+M)$

  路径的转化是这种算法的关键, 也是巧妙所在

 代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 4004, mod = 998244853;

int n, m;
ll fac[maxn], inv[maxn], f[maxn], ans = 0;

void init()
{
    fac[0] = 1LL;
    for(int i = 1; i <= n + m; ++i)
        fac[i] = 1LL * fac[i-1] * i % mod;
    inv[0] = inv[1] = 1LL;
    for(int i = 2; i <= n + m; ++i)
        inv[i] = 1LL * (mod - mod / i) * inv[mod%i] % mod;
    for(int i = 2; i <= n + m; ++i)
        inv[i] = inv[i-1] * inv[i] % mod;
}

ll comb(int x, int y)
{
    return (fac[x] * inv[y] % mod) * inv[x-y] % mod;
}

ll calc(int x)
{
    if(x <= n - m)
        return comb(n + m, n);
    return comb(n + m, n - x);
}

int main()
{
    scanf("%d%d", &n, &m);
    init();
    for(int i = 1; i <= n; ++i)
        f[i] = calc(i);
    for(int i = 1; i <= n; ++i)
        ans = (ans + (1LL * i * (f[i] - f[i+1] + mod) % mod)) % mod;
    printf("%lld\n", ans);
    return 0;
}

原文地址:https://www.cnblogs.com/Joker-Yza/p/11613901.html

时间: 2024-10-08 03:07:28

CF1024E Natasha, Sasha and the Prefix Sums——DP/数学(组合数)的相关文章

[CF1204E]Natasha,Sasha and the Prefix Sums 题解

前言 本文中的排列指由n个1, m个-1构成的序列中的一种. 题目这么长不吐槽了,但是这确实是一道好题. 题解 DP题话不多说,直接状态/变量/转移. 状态 我们定义f表示"最大prefix sum"之和 变量 f[i][j]为有i个1,j个-1的"最大prefix sum"之和 转移 我们记C[i][j]为\(\left(\begin{matrix} i \\ j\end{matrix}\right)\),那么: \[f[i][j] = \left\{\begin

CF1204E Natasha, Sasha and the Prefix Sums

题意 给\(n\)个1和\(m\)个0,定义一个01串的权值为它所有前缀和的最大值(包括0),求可以组成的所有不同串的权值和,答案对998244853取模 思路 由于数据较小,本题有个\(O(n^2)\)比较复杂的DP做法,自行百度... 实际上本题用数学规律可以\(O(n)\)做 设\(f_i\)表示权值为\(i\)的01串数量,直接求不容易,再设\(g_i\)为权值至少为\(i\)的01串数量,那么\(f_i=g_i-g_{i+1}\) 利用求卡特兰数列的一种方法:将01串看做从坐标系\((

E. Natasha, Sasha and the Prefix Sums

给定n个 1 m个 -1的全排 求所有排列的$f(a)=max(0,max_{1≤i≤l}∑_{j=1}^{i}a_{j})$之和 组合数,枚举 #include <bits/stdc++.h> using namespace std; typedef long long ll; const ll MOD = 998244853; int n, m; ll C[4002][4002]; ll sum; ll realSum; ll ans; void init() { for(int i=0;

CodeForces - 1204E Natasha, Sasha and the Prefix Sums (组合数学,卡特兰数扩展)

题意:求n个1,m个-1组成的所有序列中,最大前缀之和. 首先引出这样一个问题:使用n个左括号和m个右括号,组成的合法的括号匹配(每个右括号都有对应的左括号和它匹配)的数目是多少? 1.当n=m时,显然答案为卡特兰数$C_{2n}^{n}-C_{2n}^{n+1}$ 2.当n<m时,无论如何都不合法,答案为0 3.当n>m时,答案为$C_{n+m}^{n}-C_{n+m}^{n+1}$,这是一个推论,证明过程有点抽象,方法是把不合法的方案数等价于从(-2,0)移动到(n+m,n-m)的方案数,

Codeforces Round #581 (Div. 2)-E. Natasha, Sasha and the Prefix Sums-动态规划+组合数学

Codeforces Round #581 (Div. 2)-E. Natasha, Sasha and the Prefix Sums-动态规划+组合数学 [Problem Description] ? 给你\(n\)个\(1\),\(m\)个\(-1\),他们任意排列有\(\frac{(n+m)!}{n!\cdot m!}\)中排列,每种排列都有一个最大前缀和(可能为\(0\)),求所有排列的最大前缀和之和为多少. [Solution] ? 定义\(dp[i][j]\)表示有\(i\)个\(

USACO prefix TrieTree + DP

/* ID:kevin_s1 PROG:prefix LANG:C++ */ #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <map> #include <set> #include <algorithm> #include <cstdlib>

CodeForces 837F - Prefix Sums | Educational Codeforces Round 26

按tutorial打的我血崩,死活挂第四组- - 思路来自FXXL /* CodeForces 837F - Prefix Sums [ 二分,组合数 ] | Educational Codeforces Round 26 题意: 设定数组 y = f(x) 使得 y[i] = sum(x[j]) (0 <= j < i) 求初始数组 A0 经过多少次 f(x) 后 会有一个元素 大于 k 分析: 考虑 A0 = {1, 0, 0, 0} A1 = {1, 1, 1, 1} -> {C(

hdu-5621 KK&#39;s Point(dp+数学)

题目链接: KK's Point Time Limit: 2000/1000 MS (Java/Others)     Memory Limit: 65536/65536 K (Java/Others) Problem Description Our lovely KK has a difficult mathematical problem:He points N(2≤N≤10^5) points on a circle,there are all different.Now he's goi

CodeForces 55D Beautiful numbers 数位DP+数学

题意大概是,判断一个正整数区间内有多少个整数能被它自身的每一个非零的数字整除. 因为每一个位置上的整数集s = {0,1,2,3,4,5,6,7,8,9} lcm(s) = 2520 现在有一个整数t是由s中一个或者多个数字构成的,记为abcde,显然t = a*10^4+b*10^3+c*10^2+d*10^1+e 要使得t能被a,b,c,d,e整除,必然有t % lcm(a,b,c,d,e) = 0 因为a,b,c,d,e去重之后一定是s的一个子集,所以lcm(s)一定是lcm(a,b,c,