fzszkl有 (N) 个 (1) 和 (M) 个 (-1) 。他将它们随意排成一列,记为序列 (A) ,并求出了这个序列的前缀和,找到了前缀和的最大值(注意,第 (0) 个位置的前缀和也算,所以这个最大值一定是非负整数)。
因为fzszkl很无聊,找一个不过瘾,所以他想知道:对于所有本质不同的序列,前缀和的最大值之和是多少。
对于一个序列 (A) ,它的前缀和的最大值 (F(A)) 的严谨定义为:
答案对 (998244853) (是个质数)取模。
(N,Mle 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;
}