BZOJ2004:[HNOI2010]Bus 公交线路(状压DP,矩阵乘法)

Description

小Z所在的城市有N个公交车站,排列在一条长(N-1)km的直线上,从左到右依次编号为1到N,相邻公交车站间的距离均为1km。 作为公交车线路的规划者,小Z调查了市民的需求,决定按下述规则设计线路:
1.设共K辆公交车,则1到K号站作为始发站,N-K+1到N号台作为终点站。
2.每个车站必须被一辆且仅一辆公交车经过(始发站和终点站也算被经过)。 
3.公交车只能从编号较小的站台驶往编号较大的站台。 
4.一辆公交车经过的相邻两个
站台间距离不得超过Pkm。 在最终设计线路之前,小Z想知道有多少种满足要求的方案。由于答案可能很大,你只需求出答案对30031取模的结果。

Input

仅一行包含三个正整数N K P,分别表示公交车站数,公交车数,相邻站台的距离限制。
N<=10^9,1<P<=10,K<N,1<K<=P

Output

仅包含一个整数,表示满足要求的方案数对30031取模的结果。

Sample Input

样例一:10 3 3
样例二:5 2 3
样例三:10 2 4

Sample Output

1
3
81

HINT

【样例说明】
样例一的可行方案如下: (1,4,7,10),(2,5,8),(3,6,9)
样例二的可行方案如下: (1,3,5),(2,4) (1,3,4),(2,5) (1,4),(2,3,5)
P<=10 , K <=8

Solution 

显然这个范围是要状压+矩乘……然后我就不会了
因为一个公交车经过的两个相邻的站台之间的距离不超过$p$,所以设$f[i][S]$表示最靠左的车在$i$位置,$i$后面$p$个位置的状态是$S$,其中$S$的某一位是0代表没车,1代表有车。
这相当于我们把这$k$辆车都放到一个长度为$p$的区间内来做。因为车没有编号所以我们并不需要区分。
然后状态数最大只有$C(9,4)$,所以可以预处理出所有状态可以到达的状态然后矩阵转移……$f[i][S]=\sum f[i-1][S']$,其中$S'$状态可以转移到$S$。
初始状态为长度为$p$的区间左边一段都是1,终止状态也是。

Code

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 #define MOD (30031)
 5 using namespace std;
 6 
 7 int n,k,p,S[209],cnt,Refun;
 8 
 9 struct Matrix
10 {
11     int m[209][209];
12     Matrix(){memset(m,0,sizeof(m));}
13     Matrix operator * (const Matrix b) const
14     {
15         Matrix ans;
16         for (int i=1; i<=130; ++i)
17             for (int j=1; j<=130; ++j)
18                 for (int k=1; k<=130; ++k)
19                     (ans.m[i][j]+=m[i][k]*b.m[k][j])%=MOD;
20         return ans;
21     }
22 }A,G;
23 
24 Matrix Qpow(Matrix a,int p)
25 {
26     Matrix ans;
27     for (int i=1; i<=130; ++i) ans.m[i][i]=1;
28     while (p)
29     {
30         if (p&1) ans=ans*a;
31         a=a*a; p>>=1;
32     }
33     return ans;
34 }
35 
36 int Get(int x)//二进制下1的数量 
37 {
38     int num=0;
39     while (x) num+=(x&1),x>>=1;
40     return num;
41 }
42 
43 bool check(int x,int y)//判断x状态是否能到达y状态 
44 {
45     int now=S[x]<<1, tmp=0;
46     for (int i=0; i<p; ++i)
47         if ((now&(1<<i))!=(S[y]&(1<<i))) tmp++;
48     return tmp<=1;
49 }
50 
51 int main()
52 {
53     scanf("%d%d%d",&n,&k,&p);
54     for (int i=1<<(p-1); i<=(1<<p)-1; ++i)//强制第一位有车 
55         if (Get(i)==k)
56         {
57             S[++cnt]=i;
58             if (S[cnt]==(1<<p)-(1<<p-k)) Refun=cnt;//记录车都在起点/终点的状态 
59         }
60     A.m[1][Refun]=1;
61     for (int i=1; i<=cnt; ++i)
62         for (int j=1; j<=cnt; ++j)
63             if (check(i,j)) G.m[i][j]=1;
64     G=Qpow(G,n-k);
65     A=A*G;
66     printf("%d\n",A.m[1][Refun]);
67 }
posted @ 2018-09-26 20:06  Refun  阅读(231)  评论(0编辑  收藏  举报