【题解】P9035 「KDOI-04」Pont des souvenirs

P9035 「KDOI-04」Pont des souvenirs

题意

给定正整数 \(n,k\),求有多少个长度为 \(n\) 的正整数序列 \(a\) 满足:

  • \(0<a_1\le a_2\le a_3\le\cdots\le a_n\le k\)
  • \(\forall\ i\not=j\)\(a_i+a_j\le k+1\)

答案对 \(10^9+7\) 取模。

题解

知识点:组合数。

启发:

  • 组合数求和。

  • 插板法。

不追求严谨证明的话,这题是极为简单的,得到 \(O(nk)\) 的递推做法后把 dp 数组列出来,对着杨辉三角找规律就结束了。

这里提供一种基于隔板法的推法。

首先得知道这个恒等式。

\[\large \displaystyle \sum_{i=1}^k\binom{n+i}{i-1}=\binom{n+k+1}{k-1} \]

这是组合数平行求和公式的一种形式,用归纳法可以证明,这里就不证了。

\(n=1\) 时,答案显然为 \(k\)

现在讨论 \(n\ge 2\) 时的情况。

\(a\) 是非严格单调递增的,故第二条限制可以转化为,\(a_{n-1}+a_{n}\le k+1\)

考虑枚举 \(a_{n}\)\(a_{n-1}\),分别设为 \(i,j\),则 \(i\) 枚举范围是 \(1\sim k\)\(j\) 则为 \(1\sim \min\{k+1-i,i\}\),这样枚举出来的一定是合法的。

现在考虑对于某对 \(i,j\),如何求出 \(a_1\sim a_{n-2}\) 取值方法数。

由于 \(a\) 非严格单调递增,故可以用隔板法来求,不过某些值是可以一个都不取的,还要增加 \(j\) 个空。

假设读者已经了解隔板法,下面直接给出推出的式子。

\[\large \binom{(n-2-1)+j}{j-1}=\binom{n+j-3}{j-1} \]

故答案可以表示为如下和式。

\[\displaystyle \large \sum_{i=1}^k \sum_{j=1}^{\min\{k+1-i,i\}} \binom{n+j-3}{j-1} \]

根据上文提到的恒等式,可以去掉一个和式。

\[\displaystyle \large \sum_{i=1}^k \binom{n+\min\{k+1-i,i\}-2}{\min\{k+1-i,i\}-1} \]

式子中的 \(\min\) 很难看,考虑把式子拆成两段。

\[\displaystyle \large \sum_{i=1}^{\lfloor \frac{k}{2} \rfloor} \binom{n+i-2}{i-1}+\sum_{i=1}^{\lceil \frac{k}{2} \rceil} \binom{n+i-2}{i-1} \]

再次使用上文的恒等式,得到最终的答案式子。

\[\displaystyle \large \binom{n+\lfloor \frac{k}{2} \rfloor-1}{\lfloor \frac{k}{2} \rfloor-1}+\binom{n+\lceil \frac{k}{2} \rceil-1}{\lceil \frac{k}{2} \rceil-1}=\displaystyle \large \binom{n+\lfloor \frac{k}{2} \rfloor-1}{n}+\binom{n+\lceil \frac{k}{2} \rceil-1}{n} \]

#include<bits/stdc++.h>
using namespace std;

#define rep(i,l,r) for(int i=(l);i<=(r);++i)
#define per(i,l,r) for(int i=(r);i>=(l);--i)
#define pr pair<int,int>
#define fi first
#define se second
#define pb push_back
#define all(x) (x).begin(),(x).end()
#define sz(x) (int)(x).size()
#define bg(x) (x).begin()
#define ed(x) (x).end()

#define N 20000010
#define int long long
const int mod=1e9+7;

namespace comb{
    int fac[N];

    inline int inv(int a){
        int b=mod-2,ans=1;

        while(b){
            if(b&1){
                ans=ans*a%mod;
            }
            a=a*a%mod;
            b>>=1;
        }

        return ans;
    }

    inline int C(int n,int m){
        if(n<m){
            return 0;
        }
        return fac[n]*inv(fac[m])%mod*inv(fac[n-m])%mod;
    }

    inline void init(int lim){
        fac[0]=1;
        rep(i,1,lim){
            fac[i]=fac[i-1]*i%mod;
        }
    }
}

using comb::C;

inline void sol(){
    int n,k;

    cin>>n>>k;

    if(n==1){
        cout<<k<<"\n";
        return;
    }

    cout<<(C(n-1+k/2,n)+C(n-1+(k-1)/2+1,n))%mod<<"\n";
}

signed main(){
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);

    comb::init(2e7);

    int t;
    cin>>t;
    while(t--){
        sol();
    }

    return 0;
}
posted @ 2025-04-30 19:21  Lucyna_Kushinada  阅读(8)  评论(0)    收藏  举报