[2020多校A层11.25] 最大前缀和
fzszkl有 \(N\) 个 \(1\) 和 \(M\) 个 \(-1\) 。他将它们随意排成一列,记为序列 \(A\) ,并求出了这个序列的前缀和,找到了前缀和的最大值(注意,第 \(0\) 个位置的前缀和也算,所以这个最大值一定是非负整数)。
因为fzszkl很无聊,找一个不过瘾,所以他想知道:对于所有本质不同的序列,前缀和的最大值之和是多少。
对于一个序列 \(A\) ,它的前缀和的最大值 \(F(A)\) 的严谨定义为:
答案对 \(998244853\) (是个质数)取模。
\(N,M\le 2e3\)
早上考试遇到的题,当时没做出来,下午想出来之后感觉非常不错,所以记录下解法(而不是像某人上午对着原题题解抄一遍就会了)。
发现 \(\max\) 这个限制是不好处理的,那么我们可以枚举最大值,假设当前枚举到了最大值为 \(x\) ,那么问题转化为:
- 从 \((0,0)\) 走到 \((n,m)\) ,必须碰到 \(y=x+b\) 这条直线的方案数。
因为 \(1,-1\) 类似于一个括号序列,而我们现在多了个存在一个前缀和 \(\ge x\) 的要求。
考虑翻转坐标系,看作从 \(n+m\) 次选数,数只有 \(1,-1\) ,最后和为 \(n-m\) ,那么可以把这个问题进一步转化为:
- 从 \((0,0)\) 走到 \((n+m,n-m)\) ,每次往右上或右下走一步,且必须穿过 \(y=x\) 这条线的方案数。
然后这个问题就好做许多了,我们可以分情况讨论:
- \((0,0)\) 在 \(y=x\) 下面,\((n+m,n-m)\) 在 \(y=x\) 上面
那么不管怎么走一定会穿过 \(y=x\) ,那么我们设网上走了 \(t\) 步,可以列出如下方程:
可以解得 \(t=n\) ,那么往上走了 \(n\) 步,往下走了 \(m\) 步,就是 \({n+m}\choose n\) 。
- \((0,0)\) 在 \(y=x\) 下面,\((n+m,n-m)\) 也在 \(y=x\) 下面
这样子就会有穿不过的情况了,于是我们考虑做 \((0,0)\) 关于 \(y=x\) 的对称点 \((0,2x)\) ,那么这个问题等价于从 \((0,2x)\) 按原问题走法走到 \((n+m,n-m)\) ,而这样子就保证一定能穿过了,所以我们继续列刚才那样的方程:
可以解得 \(t=x+m\) ,那么往上走了 \(x+m\) 步,往下走了 \(n+m-(x+m)\) 步,就是 \({n+m}\choose{x+m}\) 。
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
const int N = 2e3;
const int M = 1e5;
const int p = 998244853;
using namespace std;
int n,m,inv[M + 5],fac[M + 5],ans,sm[M + 5],res;
int C(int n,int m)
{
if (m > n || n < 0)
return 0;
return 1ll * fac[n] * inv[m] % p * inv[n - m] % p;
}
int main()
{
scanf("%d%d",&n,&m);
fac[0] = 1;
for (int i = 1;i <= M;i++)
fac[i] = 1ll * fac[i - 1] * i % p;
inv[1] = 1;
for (int i = 2;i <= M;i++)
inv[i] = 1ll * (p - p / i) * inv[p % i] % p;
inv[0] = 1;
for (int i = 1;i <= M;i++)
inv[i] = 1ll * inv[i - 1] * inv[i] % p;
for (int i = 1;i <= n;i++)
{
if (i <= n - m)
ans += C(n + m,n),ans %= p;
else
ans += C(n + m,i + m),ans %= p;
}
cout<<ans<<endl;
return 0;
}