• 【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;
    }
    

     

  • 相关阅读:
    oracle 游标的使用
    mvc的表单发送ajax请求,太强大了!!!!
    报表页面的异步加载
    一道关于集合分组并进行笛卡尔积的题目思路
    EF常用操作截图
    大数乘法取模运算(二进制)
    求sqrt()底层效率问题(二分/牛顿迭代)
    CodeForces 282C(位运算)
    Codeforces Round #371 (Div. 2)(setunique)
    Codeforces Round #370 (Div. 2)(简单逻辑,比较水)
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/8067346.html
Copyright © 2020-2023  润新知