【jzoj5335】早苗

题目描述
       Sanae准备对大结界进行一次连续n天的风祭。Sanae每天可以召唤一种神风。她一共有m种神风可以召唤,但是如果有连续m天刮了m种不同的神风,环境就会遭到破坏。现在,Sanae想知道这n天她有多少种召唤神风的方案。
       由于答案可能很大,所以你只需要告诉她方案数对1000000007取模的结果即可。

输入
       两个正整数n,m,分别代表风祭的天数和Sanae能召唤的风的种数。

输出
       一个整数,为答案对1000000007取模的结果。

样例输入
3 3

样例输出
21

数据范围
       对于8%的数据,m=2
       对于另16%的数据,n<=10,m<=4
       对于48%的数据,n<=100000,m<=10
       对于80%的数据,n<=100000,2<=m<=100
       对于100%的数据,2<=m<=100,m<=n<=10^16

思路

       题目大意:求用1~m组成有n个数的序列,任意连续m个数不能互不相同的方案数

       ???任意连续m个数不能互不相同?如果我第i个放了数字j,那我前面必须放哪些数?i和i-1有什么关系?任意连续m个数不能互不相同的情况太多了吧……
       好像不管怎么想都得用减法啊……

多试试逆向

       设f[i]是用了i个数的合法方案数,h[i]是用了i个数的不合法方案数。
       f[i]+h[i]=mn
    
       求不出f[i],那我们来求h[i]吧~
       emmmmmmm,考虑h[i]和h[i-1]的关系,可以看出h[i]必定包含h[i-1]*m(意思是最后一位随便放,比f好多了),也就是h[i]=h[i-1]*m+g[i],其中g[i]为加上第i数后新出现的不合法方案数。显然,g[i]的最后m个数是个全排列,也就是g[i]包含了m!*f[i-m]。
       然而,我们这样算会算重。
       “g[i]为加上第i数后新出现的不合法方案数。”,新出现,也就是只有i-m+1~i是全排列,那么i-m~i-1,i-m-1~i-2……都不能是全排列。m!*f[i-m]不能保证这点。
       例如:m=5,你想算g[9],5 3 2 3 1 5 4 2 3中,f[4]确实包含5 3 2 3这个方案,但g[9]中不包含5 3 2 3 1 5 4 2 3,因为5 3 2 3 1 5 4早就已经不合法了。
       但我们可以去重啊。
       对于m!*f[i-m]中的每一个算重的序列,我们找到这个序列第一次出现全排列的位置假设为i-j-m+1~i-j(1<=j<=m-1),那么i-j-m+1~i-j是全排列而i-m+1~i也是全排列,那么i-j+1~i也是有j个元素的全排列(数字不一定正好是1~j,但肯定是j个元素)。那么我们就要减掉j!*g[i-j]个方案数.
       总递推式:g[i]=m!*f[i-m]-$\sum _limits{j=1}^{m-1}j!\ast g\left[ i-j\right]$
                =m!*(mi-m-h[i-m])-$\sum _limits{j=1}^{m-1}j!\ast g\left[ i-j\right]$
       其中h[i-m]=h[i-m-1]*m+g[i-m].

       目前的复杂度为O(n*m),80分。

优化
       递推式的主体可以看作是n,而n的范围n<=10^16.
       妥妥的矩阵乘法优化。
       根据递推式里,与i有关的有mi-m,h[i-m],g[i-j],所以我们把矩阵转移方向定为:

  总转移:


       转移矩阵就让大家自己思考啦。

代码:

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstdlib>
 4 #include <cmath>
 5 #include <algorithm>
 6 #include <cstring>
 7 #include <string>
 8 #include <queue>
 9 #define fo(p,q,r) for(p=q;p<=r;++p)
10 #define fow(p,q,r) for(p=q;p>=r;--p)
11 #define arclr(p,q) memset(p,q,sizeof(p))
12 using namespace std;
13 typedef long long LL;
14 
15 const LL mo=1000000007;
16 
17 LL mr[105][105][2],temp[105][105],st[105],en[105],ans;
18 
19 LL jc[105],n,m,i,j,cnt;
20 
21 void mul(LL p,LL q,LL r)
22 {
23     LL i1,j1,k1;
24     arclr(temp,0);
25     fo(i1,1,m+1)
26         fo(j1,1,m+1)
27             fo(k1,1,m+1)
28             temp[i1][j1]=(temp[i1][j1]+((mr[i1][k1][p]*mr[k1][j1][q])%mo))%mo;
29     fo(i1,1,m+1)
30         fo(j1,1,m+1)
31         mr[i1][j1][r]=temp[i1][j1];
32 }
33 
34 void build()
35 {
36     mr[1][1][0]=m;
37     fo(i,2,m-1)
38         mr[i][i+1][0]=1;
39     mr[m][m+1][0]=(2*mo-jc[m])%mo;
40     mr[m][1][0]=jc[m];
41     fo(i,2,m) mr[m][i][0]=(2*mo-jc[m-i+1])%mo;
42     mr[m+1][2][0]=1; mr[m+1][m+1][0]=m;
43         
44     fo(i,1,m+1)
45         mr[i][i][1]=1;
46 }
47 
48 void mi(LL p)
49 {
50     LL i1=p; 
51     while (i1>0)
52     {
53         if (i1&1) mul(1,0,1);
54         mul(0,0,0);
55         i1>>=1;
56     }
57 }
58 
59 int main()
60 {
61     scanf("%lld%lld",&n,&m);
62     
63     jc[0]=1; 
64     fo(i,1,m) jc[i]=(jc[i-1]*i)%mo;
65     
66     st[1]=1; 
67     fo(i,2,m+1) st[i]=0;
68     
69     build();
70 
71     fo(i,1,m+1)
72     {
73         fo(j,1,m+1) printf("%lld ",mr[i][j][0]);
74         printf("\n");
75     }
76     
77     mi(n);
78     
79     arclr(en,0);
80     fo(i,1,m+1) 
81         fo(j,1,m+1) 
82             en[i]=(en[i]+((mr[i][j][1]*st[j])%mo))%mo;
83     
84     ans=(en[1]-en[m+1]+mo)%mo;
85     printf("%lld\n",ans);
86 }
矩阵快速幂

 

转载于:https://www.cnblogs.com/Krain428571/p/7426787.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值