【XSY2990】树 组合数学 容斥

题目描述

  同 Comb Avoiding Trees

  不过只用求一项。

  \(n,k\leq {10}^7\)

题解

  不难发现一棵 \(n\) 个叶子的树唯一对应了一个长度为 \(2n-2\) 的括号序列。

  往左儿子走对应加一个左括号,退到上一个只有左儿子的点,往右儿子走对应加一个右括号,那么合法的树一定满足:每一个前缀中左括号数量 \(-\) 右括号数量 \(<m-1\)

  求括号序列个数有一种容斥做法:把左括号视为 \(1\),把右括号视为 \(-1\)。用总方案数减掉走到 \(-1\) 的方案数

  每一个走到 \(-1\) 的方案在走第一次走到 \(-1\) 后把后面的括号取反,对应一个结尾走到 \(-2\) 的方案数。

  这道题要求不能走到 \(m-1\),那么还要减掉结尾走到 \(2m-2\) 的方案数。

  但是既走到 \(-1\) 又走到到 \(2m-2\) 的方案会被减掉两次,所以要加上结尾走到 \(2m\) 的方案数和结尾走到 \(-2m\) 的方案数。

  然后又要减掉 \(0\to -1\to m-1\to -1\) 的方案数和 \(0\to m-1\to -1\to m-1\) 的方案数。

  以此类推。

  时间复杂度:\(O(n)\)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<utility>
#include<functional>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
void open(const char *s){
#ifndef ONLINE_JUDGE
    char str[100];sprintf(str,"%s.in",s);freopen(str,"r",stdin);sprintf(str,"%s.out",s);freopen(str,"w",stdout);
#endif
}
int rd(){int s=0,c,b=0;while(((c=getchar())<'0'||c>'9')&&c!='-');if(c=='-'){c=getchar();b=1;}do{s=s*10+c-'0';}while((c=getchar())>='0'&&c<='9');return b?-s:s;}
void put(int x){if(!x){putchar('0');return;}static int c[20];int t=0;while(x){c[++t]=x%10;x/=10;}while(t)putchar(c[t--]+'0');}
int upmin(int &a,int b){if(b<a){a=b;return 1;}return 0;}
int upmax(int &a,int b){if(b>a){a=b;return 1;}return 0;}
const ll p=998244353;
const int N=10000010;
int n,m;
ll fp(ll a,ll b)
{
    ll s=1;
    for(;b;b>>=1,a=a*a%p)
        if(b&1)
            s=s*a%p;
    return s;
}
int ifac[2*N];
ll binom(int x,int y)
{
    return x>=y&&y>=0?(ll)ifac[y]*ifac[x-y]%p:0;
}
int main()
{
    open("c");
    scanf("%d%d",&n,&m);
    n--;
    ll s=1;
    for(int i=1;i<=2*n;i++)
        s=s*i%p;
    ifac[2*n]=fp(s,p-2);
    for(int i=2*n;i>=1;i--)
        ifac[i-1]=(ll)ifac[i]*i%p;
    ll ans=binom(2*n,n);
    for(int i=0;i<n;i+=m)
    {
        ans-=binom(2*n,n+i+1);
        ans-=binom(2*n,n+i+m-1);
        ans+=2*binom(2*n,n+i+m);
    }
    ans=(ans%p*s%p+p)%p;
    printf("%lld\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/ywwyww/p/9178383.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值