【bzoj4870】[Shoi2017]组合数问题 dp+快速幂/矩阵乘法
題目描述
輸入
第一行有四個整數 n, p, k, r,所有整數含義見問題描述。 1 ≤ n ≤ 10^9, 0 ≤ r < k ≤ 50, 2 ≤ p ≤ 2^30 ? 1輸出
一行一個整數代表答案。樣例輸入
2 10007 2 0
樣例輸出
8
題目大意
問從nk個數中選出若干個,且選出數的數目mod k=r的方案數
題解
dp+快速冪/矩陣乘法
題目描述是騙人的,一個一個加根本不可能加的過來。
關于矩陣乘法的題解可以參考 popoqqq大爺的博客 ,時間復雜度為O(k^3logn),
我的做法是dp+快速冪。
(UPD:后來知道這其實就是循環矩陣乘法)
設 $f[i][j]$ 表示從 $i$ 個數中選出若干個,且選出的數的數目 $\text{mod} k=j$ 的方案數
那么有 $f[i1+i2][j]=\sum((j1+j2)mod?k?=?j)f[i1][j1]?f[i2][j2]$?
這里可能比較難用數學語言表述,但事實上其中的求和符號僅是對于 $j1$ 和 $j2$ ,并不對于 $i1$ 和 $i2$ ,也就是說只要找到任意一組 $i1$ 和 $i2$ ,就可以用 $f[i1]$ 和 $f[i2]$ 推出。
發現這一點類似于乘方,于是我們可以使用快速冪的方法來快速推出f[nk]數組,時間復雜度為O(k^2logn).
注意一下這里k可能等于1,所以初始化時不能簡單地將f[0][1]賦為1,而是將f[0][1%k]加上1。
#include <cstdio> #include <cstring> typedef long long ll; int p , k; struct data {ll f[60];data(){memset(f , 0 , sizeof(f));}data operator*(const data a)const{int i , j;data tmp;for(i = 0 ; i < k ; i ++ )for(j = 0 ; j < k ; j ++ )tmp.f[(i + j) % k] = (tmp.f[(i + j) % k] + f[i] * a.f[j]) % p;return tmp;} }ans; data pow(data x , ll y) {data ans;ans.f[0] = 1;while(y){if(y & 1) ans = ans * x;x = x * x , y >>= 1;}return ans; } int main() {int n , r;scanf("%d%d%d%d" , &n , &p , &k , &r);ans.f[0] ++ , ans.f[1 % k] ++ ;printf("%lld\n" , pow(ans , (ll)n * k).f[r]);return 0; }轉載于:https://www.cnblogs.com/GXZlegend/p/6855497.html
總結
以上是生活随笔為你收集整理的【bzoj4870】[Shoi2017]组合数问题 dp+快速幂/矩阵乘法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何用windbg查看_eprocess
- 下一篇: Linux操作系统下/etc/hosts