[SCOI2010]生成字符串

题面
首先打广告: 由于这题需要用到逆元,所以!!!

如果不会逆元的请看这里 − − − − − > -----> > 详解数论从入门到入土
想更好地把逆元应用到欧拉函数、欧拉定理、费马小定理、中国剩余定理请看这里 − − − − − > -----> >数论1
想更好地把逆元应用到组合数、扩展欧几里得请看这里 − − − − − > -----> >数论2
okk 上题解

这道题应用到了小学奥数 概率论中的一个比较骚的操作
可以考虑把 1 1 1的个数与 0 0 0的个数的和看成 x x x坐标, 1 1 1的个数与 0 0 0的个数的差看成 y y y坐标,那么如下图:
[SCOI2010]生成字符串_第1张图片
向右上走( x x x坐标加 1 1 1 y y y坐标加 1 1 1)就表示这个字符选择 1 1 1
向右下走( x x x坐标加 1 1 1 y y y坐标减 1 1 1)就表示这个字符选择 0 0 0
那么显然从 ( 0 , 0 ) (0,0) (0,0) n + m n+m n+m步正好到 ( n + m , n − m ) (n+m,n-m) (n+m,nm)的方案数就是 C ( n + m , m ) C(n+m,m) C(n+m,m)
可以理解为从 n + m n+m n+m步中抽出 m m m步向右下走
考虑限制条件:任意前缀中 1 1 1的个数不少于 0 0 0的个数,也就是这条路径不能经过直线 y = − 1 y=−1 y=1 那么我们又要用一个东西叫做反射原理:

从点 A A A到点 B B B的路径中 经过这个点下方直线 l l l的方案数,就等于点 A A A关于直线 l l l对称的点 A ′ A' A到点 B B B的方案数。
很好理解对吧 画画图就可以看出来了

所以从 ( 0 , 0 ) (0,0) (0,0) ( n + m , n − m ) (n+m,n-m) (n+m,nm)经过 y = − 1 y=-1 y=1的方案数 就是 ( 0 , − 2 ) (0,-2) (0,2) 到点 ( n + m , n − m ) (n+m,n-m) (n+m,nm)的方案数
即从 ( 0 , − 2 ) (0,-2) (0,2)向右上走 n + 1 n+1 n+1步 向右下走 m − 1 m-1 m1步的方案数 就等于 C ( n + m , m − 1 ) C(n+m,m-1) C(n+m,m1)

那么符合题意的方案数就是总方案数减去不符合题意的方案数:
C ( n + m , m ) − C ( n + m , m − 1 ) C(n+m,m)-C(n+m,m-1) C(n+m,m)C(n+m,m1)
直接 O ( n ) O(n) O(n)预处理逆元就行
如果不会逆元的请看这里 − − − − − > -----> > 详解数论从入门到入土
想更好地把逆元应用到欧拉函数、欧拉定理、费马小定理、中国剩余定理请看这里 − − − − − > -----> >数论1
想更好地把逆元应用到组合数、扩展欧几里得请看这里 − − − − − > -----> >数论2

#include
using namespace std;
#define N int(1e6+100)
typedef long long ll;
const ll mod=20100403;
ll n,m,inv[N<<1],fac[N<<1],facinv[N<<1];
ll C(ll n, ll m){return fac[n]*facinv[m]%mod*facinv[n-m]%mod;}
int main(){
    scanf("%lld%lld",&n,&m);
    inv[1]=fac[0]=fac[1]=facinv[1]=facinv[0]=1;
    for(int i=2;i<n+m+100;i++)
    {
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;
        facinv[i]=facinv[i-1]*inv[i]%mod;
        fac[i]=fac[i-1]*i%mod;
    }
    printf("%lld\n",(C(n+m,m)-C(n+m,m-1)+mod)%mod);
}

你可能感兴趣的:(数论)