【HDU 7077】Pty loves sequence (组合计数)

题目链接

【题意】

​ 询问有多少个长度为 n n n 的正整数序列 { a i } \{a_i\} {ai}满足以下条件:
LIS ( { a i } ) = max ⁡ { a i } \text{LIS}(\{a_i\})=\max\{a_i\} LIS({ai})=max{ai}
​ 也就是最长上升子序列的长度等于序列的最大值。换句话说,如果序列最大值是 k k k ,那么满足条件的序列等价于其中存在子序列 1 , 2 , . . k 1,2,..k 1,2,..k

n ≤ 3000 n\le 3000 n3000

​ 此外,还要询问这些序列中,每个 [ 1 , n ] [1,n] [1,n] 的整数分别出现的总次数。

​ 为了防止打表通过此题,每组数据输入一个模数 p ( ≤ 1 0 9 ) p(\le 10^9) p(109) ,输出所有答案模意义下的结果。

【思路】

​ 给每个符合要求的序列确定一个唯一标识,将有利于计数。

​ 令 f ( n , k ) f(n,k) f(n,k) 表示长度为 n n n 的序列,且序列中的最大值为 k k k 的序列方案数。

​ 为了方便表示 f ( n , k ) f(n,k) f(n,k) 我们对每个序列用其最靠前(即下标字典序最小的)的 1 , 2 , . . . k 1,2,...k 1,2,...k 子序列作为标识。

​ 假设这 k k k 个标识位已经确定,我们发现对于标识位的这 k k k 个数字,

1 1 1 前面的数字只能塞 2 , 3 , . . . k 2,3,...k 2,3,...k 这些数字,

1 , 2 1,2 1,2 之间只能塞 1 , 3 , . . . k 1,3,...k 1,3,...k 这些数字

​ ……

k − 1 , k k-1,k k1,k 之间只能塞 1 , 2 , . . . k − 1 1,2,...k-1 1,2,...k1 这些数字。

​ 而 k k k 后面可以塞不超过 k k k 的任意数字。

​ 所以假设 k k k 后面有 d d d 个数字,那么算上前面的 n − k − d n-k-d nkd 个空位,一共有 k d ( k − 1 ) n − k − d k^d(k-1)^{n-k-d} kd(k1)nkd 种情况。

​ 所以我们考虑枚举 d d d ,并且用组合数 ( n − d − 1 k − 1 ) \binom{n-d-1}{k-1} (k1nd1) 来表示对 k k k 个标识位的确定。

​ 于是乎得到
f ( n , k ) = ∑ d = 0 n − k ( n − d − 1 k − 1 ) k d ( k − 1 ) n − k − d f(n,k) = \sum_{d=0}^{n-k}\binom{n-d-1}{k-1} k^d(k-1)^{n-k-d} f(n,k)=d=0nk(k1nd1)kd(k1)nkd
​ 那么序列计数答案就是:
∑ k = 1 n f ( n , k ) \sum_{k=1}^n f(n,k) k=1nf(n,k)
​ 预处理组合数和幂次,可以 O ( n 2 ) O(n^2) O(n2) 解决。

​ 接下来考虑每个数字出现了多少次,我们考虑 [ 1 , k ] [1,k] [1,k] f ( n , k ) f(n,k) f(n,k) 序列中出现的次数。

​ 根据上面推导过程中,每个位置可以塞数字的情况,容易发现 k k k 个数字的出现次数是均匀的(其实感知一下也能猜出这个性质),所以每个数字在最大值为 k k k 的合法序列中的出现次数是:
n k ∑ d = 0 n − k ( n − d − 1 k − 1 ) k d ( k − 1 ) n − k − d \frac{n}{k}\sum_{d=0}^{n-k}\binom{n-d-1}{k-1} k^d(k-1)^{n-k-d} knd=0nk(k1nd1)kd(k1)nkd
​ 可以发现对于 d > 0 d>0 d>0 的时候,外面的 1 k 1\over k k1 可以直接乘进去,只有 d = 0 d=0 d=0 的时候,需要特殊处理一下这一项,即:
n k ( n − 1 k − 1 ) ( k − 1 ) n − k = n ⋅ ( n − 1 ) ! k ⋅ ( k − 1 ) ! ( n − k ) ! ( k − 1 ) n − k = ( n k ) ( k − 1 ) n − k \frac{n}{k} \binom{n-1}{k-1}(k-1)^{n-k}=\frac{n\cdot (n-1)!}{k\cdot(k-1)!(n-k)!}(k-1)^{n-k}=\binom{n}{k}(k-1)^{n-k} kn(k1n1)(k1)nk=k(k1)!(nk)!n(n1)!(k1)nk=(kn)(k1)nk
​ 因此我们可以定义 g ( x , k ) g(x,k) g(x,k) 表示数字 x x x 在序列最大值是 k k k 的合法序列中的出现次数,并且根据刚才的推导,有:
g ( x , k ) = { 0 x > k ( n k ) ( k − 1 ) n − k + n ∑ d = 1 n − k ( n − d − 1 k − 1 ) k d − 1 ( k − 1 ) n − k − d x ≤ k g(x,k)=\begin{cases} 0 & x>k \\ \binom{n}{k}(k-1)^{n-k}+ n\sum_{d=1}^{n-k}\binom{n-d-1}{k-1} k^{d-1}(k-1)^{n-k-d} & x\le k \end{cases} g(x,k)={0(kn)(k1)nk+nd=1nk(k1nd1)kd1(k1)nkdx>kxk
​ 求出之后,数字 x x x 的总出现次数就是 ∑ i = x n g ( x , k ) \sum_{i=x}^{n}g(x,k) i=xng(x,k) 其实就是后缀和啦。

​ 同理这个式子也就可以 O ( n 2 ) O(n^2) O(n2) 求解了。

【代码】
#pragma GCC optimize ("O3")
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAXN 3010
using namespace std;
int n,p;
int c[MAXN][MAXN];
int power[MAXN][MAXN];
void get_c()
{
    c[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        c[i][0]=1;
        for(int j=1;j<=i;j++)
            c[i][j]=(c[i-1][j-1]+c[i-1][j])%p;
    }
    for(int i=0;i<=n;i++)
    {
        power[i][0]=1;
        for(int j=1;j<=n;j++)
            power[i][j]=1ll*power[i][j-1]*i%p;
    }
}

int s[MAXN];
int main()
{
    
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&p);
        get_c();
        int ans=0;
        s[n+1]=0;
        for(int k=n;k>=1;k--)
        {
            for(int d=0;d<=n-k;d++)
            {
                long long tmp=1ll*c[n-d-1][k-1]*power[k-1][n-k-d]%p;
                ans=(ans+tmp*power[k][d])%p;
                if(d>0)
                    s[k]=(s[k]+tmp*power[k][d-1]%p*n)%p;
                else
                    s[k]=1ll*c[n][k]*power[k-1][n-k]%p;
            }
            s[k]=(s[k+1]+s[k])%p;
        }
        printf("%d\n",ans);
        for(int i=1;i<=n;i++)printf("%d%c",s[i],i==n?'\n':' ');
    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值