【bzoj2004】[Hnoi2010]Bus 公交线路 状压dp+矩阵乘法

题目描述

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

输入

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

输出

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

样例输入

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

样例输出

1
3
81


题解

状压dp+矩阵乘法

很容易想到以当前枚举位置下每辆车最后的出现位置为状态,即 $f[i][j]$ 表示 $i$ 个位置,每辆车最后的出现位置状态为 $j$ 的方案数。

那么考虑转移:如果某个车的位置是从左到右 $p$ 个中的第 $1$ 个,则必须移动这辆车;否则可以移动任意一辆车。

但是这样状态难以存下($A_p^k$)

仔细思考可以发现,每辆车都是一样的,因此没有顺序之分,状态数从排列变为了组合($C_p^k$),最多只有 $C_{10}^5=252$ 。

而且进一步可以通过dp的意义得到:状态中一定有一辆车在最后一个位置。因此最多只有 $C_9^4=126$ 。

按照这个思路容易想到矩阵乘法。

设二进制状态表示从左到右的 $p$ 个位置中,哪些位置是 $k$ 辆车的最后出现的位置。

那么初始状态和结束状态都可以看成前 $p-k$ 个位置为空,后 $k$ 个位置全有车。显然需要转移 $n-k$ 次。

因此首先预处理状态和转移,然后直接求矩阵的 $n-k$ 次方计算即可。

时间复杂度 $O((C_{p-1}^{k-1})^3log n)$ 

#include <cstdio>
#include <cstring>
#include <algorithm>
#define mod 30031
using namespace std;
int c[1030] , w[1030] , v[130] , n;
struct data
{
	int v[130][130];
	data() {memset(v , 0 , sizeof(v));}
	int *operator[](int a) {return v[a];}
	data operator*(data &a)
	{
		data ans;
		int i , j , k;
		for(i = 1 ; i <= n ; i ++ )
			for(j = 1 ; j <= n ; j ++ )
				for(k = 1 ; k <= n ; k ++ )
					ans[i][j] = (ans[i][j] + v[i][k] * a[k][j]) % mod;
		return ans;
	}
}A;
data pow(data x , int y)
{
	data ans;
	int i;
	for(i = 1 ; i <= n ; i ++ ) ans[i][i] = 1;
	while(y)
	{
		if(y & 1) ans = ans * x;
		x = x * x , y >>= 1;
	}
	return ans;
}
int main()
{
	int m , k , p , i , j;
	scanf("%d%d%d" , &m , &k , &p);
	for(i = 1 ; i < (1 << p) ; i ++ )
	{
		c[i] = c[i - (i & -i)] + 1;
		if(c[i] == k && i & (1 << (p - 1))) w[i] = ++n , v[n] = i;
	}
	for(i = 1 ; i <= n ; i ++ )
	{
		if(v[i] & 1) A[i][w[(1 << (p - 1)) | (v[i] >> 1)]] = 1;
		else
			for(j = 0 ; j < p ; j ++ )
				if(v[i] & (1 << j))
					A[i][w[(1 << (p - 1)) | ((v[i] ^ (1 << j)) >> 1)]] = 1;
	}
	A = pow(A , m - k);
	i = w[(1 << p) - (1 << (p - k))];
	printf("%d
" , A[i][i]);
	return 0;
}

 

原文地址:https://www.cnblogs.com/GXZlegend/p/8067346.html