【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})^3\log 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\n" , A[i][i]);
	return 0;
}

 

 

转载于:https://www.cnblogs.com/GXZlegend/p/8067346.html

标题基于Python的自主学习系统后端设计与实现AI更换标题第1章引言介绍自主学习系统的研究背景、意义、现以及本文的研究方法和创新点。1.1研究背景与意义阐述自主学习系统在教育技术领域的重要性和应用价值。1.2国内外研究现分析国内外在自主学习系统后端技术方面的研究进展。1.3研究方法与创新点概述本文采用Python技术栈的设计方法和系统创新点。第2章相关理论与技术总结自主学习系统后端开发的相关理论和技术基础。2.1自主学习系统理论阐述自主学习系统的定义、特征和理论基础。2.2Python后端技术栈介绍DjangoFlask等Python后端框架及其适用场景。2.3数据库技术讨论关系型和非关系型数据库在系统中的应用方案。第3章系统设计与实现详细介绍自主学习系统后端的设计方案和实现过程。3.1系统架构设计提出基于微服务的系统架构设计方案。3.2核心模块设计详细说明用户管理、学习资源管理、进度跟踪等核心模块设计。3.3关键技术实现阐述个性化推荐算法、学习行为分析等关键技术的实现。第4章系统测试与评估对系统进行功能测试和性能评估。4.1测试环境与方法介绍测试环境配置和采用的测试方法。4.2功能测试结果展示各功能模块的测试结果和问题修复情况。4.3性能评估分析分析系统在高并发等场景下的性能表现。第5章结论与展望总结研究成果并提出未来改进方向。5.1研究结论概括系统设计的主要成果和技术创新。5.2未来展望指出系统局限性并提出后续优化方向。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值