「HNOI2010」公交线路 矩阵快速幂优化DP

「HNOI2010」公交线路

写在前面:

看完题面,这很动态规划,再看一下n的数据范围,可以联想到矩阵快速幂。(可是考场上也就到此为止了)

Part1:

一开始想到的是一个数量为 K P K^{P} KP的状态,就是存当前决策点前P个站分别是哪辆公交车的站点(可以状压)。然后枚举第i个站是那辆公交车的站点进行转移。又对于每辆公交车由于只有离当前决策点最近的站才对后面的决策有影响,故只用存这个位置即可。

然后我们观察转移,发现我们其实不需要知道这前P个站每一个站归属于哪辆公交车。(无论归属于哪辆公交车,他们的转移都是类似的)。故我们只用记录着前P个站是不是某辆公交车的离决策点最近的一个站点再进行转移即可。

故最后状态 d p [ i ] [ j ] dp[i][j] dp[i][j]表示已经决策了前i个站,j是一个二进制数表示 i i i以及 i i i前面P个站点的信息。

还要注意一点,就是如果离它距离为P的站点是某个公交车离决策点最近的点,那么当前决策点一定是该公交车的站点。

这样的复杂度就是 O ( 2 P ⋅ n ) O(2^P\cdot n) O(2Pn)可以过 n ≤ 1000 n\leq 1000 n1000的数据。

暴力代码:

int main(){
	int n,K,lim;
	scanf("%d%d%d",&n,&K,&lim);
	int I=(1<<lim)-1,II=(1<<K)-1;
	dp[K][II]=1;//初始状态
	for(int step=K;step<n;step++){
		for(int chs=II;chs<=I;chs++){
			if(!dp[step][chs])continue;
			if((1<<(lim-1))&chs){//上面说的要注意的地方
				Add(dp[step+1][((chs<<1)&I)+1],dp[step][chs]);//带取模的加
			}else{
				for(int now=lim-1;now>=1;now--){
					if(!((1<<(now-1))&chs))continue;//如果这个站不是离决策点最近的点就无法转移
					Add(dp[step+1][(((chs-(1<<(now-1)))<<1)&I)|1],dp[step][chs]);
				}
			}
		}
	}
	printf("%d\n",dp[n][II]);//最终状态
	return 0;
}

Part 2

(突然发现这很矩乘),矩阵快速幂解这种累计方案的题最好用了。

然后直接扔上去一个裸的矩乘,复杂度是 O ( ( 2 P ) 3 ⋅ log ⁡ n ) O((2^{P})^{3}\cdot \log n) O((2P)3logn)。稳稳地TLE。

然后我们发现,每一个状态里1的个数肯定是K个,又因为离i距离为0的这个点一定是某个公交车的离i的最近的站(这不废话),于是二进制状态的第1位一定是1。于是状态就最多只有126(应该是吧)种了,稳健一点数组开300。然后就可以快乐AC了。

#include
#include
#define M 1005
#define P 30031
void Add(int &x,int y){//带取模的加法 
	x+=y;
	if(x>=P)x-=P;
}
int ID[1<<10];
int Get_cnt(int x){//判断二进制数下x有几个1 
	int cnt=0;
	while(x){x-=x&-x;cnt++;}
	return cnt;
}
int K,lim,sz;
void Init(){//减少状态数量 
	sz=0;
	int I=(1<<lim)-1;
	for(int i=0;i<=I;i++)if(Get_cnt(i)==K&&(i&1))ID[i]=++sz;//判断状态是否合法(也就是有没有可能出现) 
}
struct Matrix{//矩乘板子 
	int n,m;
	int num[305][305];
	void clear(){memset(num,0,sizeof(num));}
	void reset(int n,int m){this->n=n;this->m=m;}
	void Init(){for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)num[i][j]=i==j;}
	Matrix operator *(const Matrix &x)const{
		Matrix res;
		res.reset(n,x.m);
		for(int i=1;i<=res.n;i++){
			for(int j=1;j<=res.m;j++){
				res.num[i][j]=0;
				for(int k=1;k<=m;k++)Add(res.num[i][j],1ll*num[i][k]*x.num[k][j]%P);
			}
		}
		return res;
	}
}A,B,res;
Matrix Mul(Matrix a,int b){
	res.reset(a.n,a.m);
	res.Init();
	while(b){
		if(b&1)res=res*a;
		a=a*a;
		b>>=1;
	}
	return res;
}
int main(){
	int n;
	scanf("%d%d%d",&n,&K,&lim);
	Init();
	int I=(1<<lim)-1,II=(1<<K)-1;
	A.reset(1,sz);//sz为状态数 
	A.clear();
	A.num[1][ID[II]]=1;//初始状态 
	B.reset(sz,sz);B.clear();
	for(int chs=II;chs<=I;chs++){//预处理转移数组 
		if(!ID[chs])continue;
		if((1<<(lim-1))&chs){
			int nxt=ID[((chs<<1)&I)+1];
			B.num[ID[chs]][nxt]++;
		}else{
			for(int now=lim-1;now>=1;now--){
				if(!((1<<(now-1))&chs))continue;
				int nxt=ID[(((chs-(1<<(now-1)))<<1)&I)|1];
				B.num[ID[chs]][nxt]++;
			}
		}
	}
	B=Mul(B,n-K);
	A=A*B;
	printf("%d\n",A.num[1][ID[II]]);
	return 0;
}

你可能感兴趣的:(DP,矩阵快速幂)