[ARC124F]Chance Meeting

壹、题目描述 ¶

传送门 to Atcoder.

贰、题解 ¶

相当于把题解翻译一遍了......

将两个东西相碰转化为,在某一时刻两个人所处行列均相同。接下来,出现的 \(n,m\) 均为原题目减一(由行列长度变成了步数)

注意到无论两个东西怎么走,在经过了 \(n\) 次竖直操作之后,他们俩必定在同一行,因而,竖直操作的细节以并不是那么重要,我们只需要考虑他们在第 \(n\) 行相遇的情形即可(其他情况是完全一样的)。

接下来,我们假设 \(f(i)\) 表示两个东西在 \((n, i)\) 第一次相遇的方案数,不难发现总的答案即

\[\sum_{i=0}^m{2n\choose n}f(i)f(m-i) \]

因为从相遇点 \((n,i)\) 走到各自终点,实际上就是一个对称。

现在,我们希望能比较快速地求出 \(f(i)\) 的值,然后用己就可以得到答案。

事实上,我们发现它的限制 “第一次” 很烦人,我们尝试使用容斥将其解决掉。

考虑设 \(g(i)\) 表示两个人在 \((n, i)\) 相遇(并不一定第一次)的方案数,那么显然有

\[g(i)={2i+n\choose 2i}{2i\choose i} \]

组合意义很简单吧?

然后,我们考察 \(f(i)\)\(g(i)\) 的关系,显然会有 \(f(i)=g(i)-C\),其中 \(C\) 我们目前并不确定。

显然,在 \(g(i)\) 中,多出了那些在 \((n, i)\) 之前即相遇的方案数,我们不妨枚举他们倒数第二次相遇在 \((n,k)\) 的方案,然后从 \(g(i)\) 中减掉,那么就有

\[f(i)=g(i)-\sum_{k=0}^{k-1}g(k)\times 2\times C(i-k-1) \]

至于这个式子是什么意思呢?由于它们倒数第二次在 \((n, k)\) 相遇,那么,他们一起,从 \((n, k)\) 走到 \((n, i)\) 的过程中,一定是一个人走在前面,另一个走在后面,并且不能出现 “超车” 的现象,这挺像卡塔兰数,事实上它就是卡塔兰数......为什么有个 \(2\),因为每个人都有机会走在前面。

然后,大家一起卷一卷就好了。

时间复杂度 \(\mathcal O(m\log m)\). 不过跑得好像挺慢,是我常数的原因?鬼知道,反正用 vector 实现很慢就是了。

叁、参考代码 ¶

#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;

#define NDEBUG
#include<cassert>

namespace Elaina{
    #define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
    #define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
    #define fi first
    #define se second
    #define mp(a, b) make_pair(a, b)
    #define Endl putchar('\n')
    #define mmset(a, b) memset(a, b, sizeof a)
    // #define int long long
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;
    typedef pair<ll, ll> pll;
    template<class T>inline T fab(T x){ return x<0? -x: x; }
    template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); }
    template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); }
    template<class T>inline T readin(T x){
        x=0; int f=0; char c;
        while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
        for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
        return f? -x: x;
    }
    template<class T>inline void writc(T x, char s='\n'){
        static int fwri_sta[1005], fwri_ed=0;
        if(x<0) putchar('-'), x=-x;
        do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
        while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
        putchar(s);
    }
}
using namespace Elaina;

const int mod=998244353;
const int primitive_root=3;
const int inverse_primitive_root=332748118;
const int maxn=2e5*3;

inline int qkpow(int a, int n){
    int ret=1;
    for(; n>0; n>>=1, a=1ll*a*a%mod)
        if(n&1) ret=1ll*ret*a%mod;
    return ret;
}

namespace NTT{
    int G[2][55], invn, n;
    vector<int>rev;
    inline void initial(){
        for(int j=1; j<=50; ++j){
            G[0][j]=qkpow(primitive_root, (mod-1)/(1<<j));
            G[1][j]=qkpow(inverse_primitive_root, (mod-1)/(1<<j));
        }
    }
    inline void prepare(int len){
        for(n=1; n<len; n<<=1);
        invn=qkpow(n, mod-2);
        rev.resize(n);
        for(int i=0; i<n; ++i)
            rev[i]=(rev[i>>1]>>1)|((i&1)? (n>>1): 0);
    }
    inline void ntt(vector<int>&f, int opt){
        f.resize(n);
        for(int i=0; i<n; ++i) if(i<rev[i])
            swap(f[i], f[rev[i]]);
        for(int p=2, level=1; p<=n; p<<=1, ++level){
            int w=G[opt][level], len=(p>>1);
            for(int k=0; k<n; k+=p){
                int buf=1, tmp;
                for(int i=k; i<k+len; ++i, buf=1ll*buf*w%mod){
                    tmp=1ll*f[i+len]*buf%mod;
                    f[i+len]=(f[i]+mod-tmp)%mod;
                    f[i]=(f[i]+tmp)%mod;
                }
            }
        }
        if(opt==1) for(int i=0; i<n; ++i)
            f[i]=1ll*f[i]*invn%mod;
    }
}

int finv[maxn+5], fac[maxn+5];
inline void prelude(){
    fac[0]=1;
    for(int i=1; i<=maxn; ++i)
        fac[i]=1ll*fac[i-1]*i%mod;
    finv[maxn]=qkpow(fac[maxn], mod-2);
    for(int i=maxn-1; i>=1; --i)
        finv[i]=1ll*finv[i+1]*(i+1)%mod;
    finv[0]=1;
}
inline int C(int n, int m){
    if(n<m) return 0;
    return 1ll*fac[n]*finv[m]%mod*finv[n-m]%mod;
}

int n, m;

vector<int>g, c, f;

signed main(){
    prelude(); NTT::initial();

    n=readin(1)-1, m=readin(1)-1;
    g.resize(m+1), c.resize(m+1);
    for(int i=0; i<=m; ++i)
        g[i]=1ll*C(2*i+n, 2*i)*C(2*i, i)%mod;
    for(int i=0; i<=m; ++i)
        c[i]=1ll*C(2*i, i)*finv[i+1]%mod*fac[i]%mod;

    NTT::prepare((m+1)<<1);
    NTT::ntt(g, 0), NTT::ntt(c, 0);
    for(int i=0; i<NTT::n; ++i)
        c[i]=1ll*c[i]*g[i]%mod;
    NTT::ntt(g, 1), NTT::ntt(c, 1);
    g.resize(m+1), c.resize(m+1), f.resize(m+1);
    for(int i=1; i<=m; ++i)
        f[i]=(0ll+g[i]+mod-2ll*c[i-1]%mod)%mod;
    f[0]=1;
    
    int ans=0;
    for(int i=0; i<=m; ++i)
        ans=(ans+1ll*f[i]*f[m-i]%mod)%mod;
    ans=1ll*ans*C(2*n, n)%mod;

    writc(ans);
    return 0;
}

肆、关键之处 ¶

能发现他们在哪一行相遇都不重要这一点是最关键的,因为它移除了一个无关的变量 \(n\),这样能够让我们更好地处理相遇。

另,计算方案时不用过于着急列出包含所有情况的答案,这种分布计数的思想是十分重要的。

posted @ 2021-07-29 16:40  Arextre  阅读(160)  评论(0编辑  收藏  举报