[BZOJ 4870] 组合数问题

Link:

传送门

Solution:

组合数的式子都可以先想想能不能递推,写出来就是:

$\sum C_{n*k}^{i*k+r}=\sum C_{n*k-1}^{i*k+r}+\sum C_{n*k-1}^{i*k+r-1}$

如果将每个求和看成一个整体,设$dp[n][r]=\sum C_{n}^{i*k+r}$,

则有$dp[n][r]=dp[n-1][r]+dp[n-1][(r-1+k)modk]$

由于$r$就相当于余数因此0-1后要变为$k-1$!

 

这样的递推式明显可以矩乘,直接上的话就是:

$新列向量=n*n矩阵\times 原列向量$,第$i$行将$s[i][i],s[i][(i-1+k)modk]$置1即可

 

不过注意这是一个循环矩阵,那么其实只要计算第一列,其他列都是其转动的结果

对于某一列有贡献的只有$n^2$个乘积,如果将每一对都转化成第一列的坐标发现是:

$s[k]=\sum_i \sum_j [(i+j)modn==k]s[i]*s[j]$ (下标从0开始) 

而之所以$答案列向量\times 第一列$也是这个式子感觉要从算贡献来考虑,可能是个巧合?

Code:

#include <bits/stdc++.h>

using namespace std;
#define X first
#define Y second
#define pb push_back
typedef double db;
typedef long long ll;
typedef pair<int,int> P;
const int MAXN=55;
int n,p,r,k,res[MAXN],a[MAXN],t[MAXN];

void mul(int *x,int *y)
{
    memset(t,0,sizeof(t));
    for(int i=0;i<=k;i++)
        for(int j=0;j<=k;j++)
            (t[(i+j)%k]+=1ll*x[i]*y[j]%p)%=p;
    for(int i=0;i<=k;i++) x[i]=t[i];
}

int main()
{
    scanf("%d%d%d%d",&n,&p,&k,&r);
    res[0]=1;a[0]++;a[1%k]++;
    
    for(ll idx=1ll*n*k;idx;idx>>=1,mul(a,a))
        if(idx&1) mul(res,a);
    printf("%d",res[r]);
    return 0;
}

 

posted @ 2018-11-23 19:38  NewErA  阅读(202)  评论(0编辑  收藏  举报