[HNOI2002]公交车路线

[HNOI2002]公交车路线

注:今天做到的有点意思的题目。

题意简述

8个站点,从A 出发一共走n 步到E(中途不可经过E),问一共有多少种走法?

图:

思路一:矩阵(假弗洛伊德)

这个题感觉和矩阵半毛钱关系都没有,那可能用矩阵?

不过不慌哈,让我们细细道来。

我们设dp[i][j] 为从ijm种方式。

dp[i][j]=k=1ndp[i][k]dp[k][j]

这是显然的,因为此时从 ikdp[i][k] 种方式,从 kjdp[k][j] 种方式,乘法原理一下,就可以得到以上方程。

题外话:不知各位是否有一种熟悉的感觉,看看以下式子(弗洛伊德):

dis[i][j]=k=1nmin(dis[i][k]+dis[k][j],dis[i][j])

嘿嘿,好了回到这个题。

上面这个式子又和矩阵有什么关系呢?

让我们先看看矩阵乘法的表达式:

A(nn),B(nn)C=ABC[i][j]=k=1nA[i][k]B[k][j]

emm....不能说一模一样吧,但可以说完全一致。

所以结果就显然了。

但是!还没有完,我们只有转移方程但没有最开始的矩阵,就像数学归纳法一样只有n>=2时成立,但n=1时不成立又有什么用呢?

以下依据 dp[i][j] 为从ij的方式给出基础矩阵:

[ZABCDEFGHA01000001B10100000C01010000D00101000E00000000F00001010G00000101H10000010]

解释:行表示出发点,列表示到达到点,其中由于不可以经过E所以E所在行为0

时间复杂度

O(82logn)

CODE

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn = 5e5+32;
ll int_maxn=1e9;
ll ll_maxn=1e18;
inline ll read_int(){
    ll a=0,f=0,g=getchar();
    while(g<'0'||g>'9'){if(g=='-') f=1;g=getchar();}
    while('0'<=g&&g<='9') a=a*10+g-'0',g=getchar();
    return f ? -a : a;
}
inline void write(ll s,bool f,int cnt=0){
    ll top=0,a[40];
    if(s<0) s=-s,putchar('-');
    while(s) a[++top]=s%10,s/=10;
    if(top==0) a[++top]=0;
    while(top) putchar(a[top]+'0'),top--;
    if(f) putchar('\n');
    while(cnt--) putchar(' ');
}

const int maxm=10;

int lin[9][9]={
//    a b c d e f g h
	0,0,0,0,0,0,0,0,0,
	0,0,1,0,0,0,0,0,1,
	0,1,0,1,0,0,0,0,0,
	0,0,1,0,1,0,0,0,0,
	0,0,0,1,0,1,0,0,0,
	0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,1,0,1,0,
	0,0,0,0,0,0,1,0,1,
	0,1,0,0,0,0,0,1,0,
};

int mod=1000;

struct Matrix{
	ll n,m;
	ll matrix[maxm][maxm];
	inline void clear(){n=m=0,memset(matrix,0,sizeof matrix);}
	inline void jc(ll M){
		clear();
		n=m=M;
		for(ll i=1;i<=n;i++) matrix[i][i]=1;
	}
	Matrix operator * (Matrix a)const{
		Matrix b;
		b.clear();
		b.n=n,b.m=a.m;
		for(ll i=1;i<=b.n;i++){
			for(ll e=1;e<=b.m;e++){
				for(ll k=1;k<=m;k++){
					b.matrix[i][e]+=matrix[i][k]*a.matrix[k][e];
					b.matrix[i][e]%=mod;
				}
			}
		}
		return b;
	}
	friend inline Matrix qpow(Matrix a,ll C){
		Matrix base;
		base.jc(a.n);
		while(C){
			if(C&1) base=base*a;
			a=a*a,C>>=1;
		}
		return base;
	}
	inline void write_(bool a){
		if(a) write(n,0),putchar(' '),write(m,1);
		for(ll i=1;i<=n;i++){
			for(ll e=1;e<=m;e++){
				write(matrix[i][e],0),putchar(' ');
			}
			puts("");
		}
	}
	inline void td(){
		clear();
		n=m=8;
		for(int i=1;i<=8;i++){
			for(int e=1;e<=8;e++){
				matrix[i][e]=lin[i][e];
			}
		}
	}
	inline int add_m(int cnt){
		ll ans=0;
		for(int i=1;i<=n;i++){
			ans+=matrix[i][cnt];
			ans%=mod;
		}
		return ans;
	}
};

inline void read(){
	int n=read_int();
	Matrix A;
	A.td();
	A=qpow(A,n);
	write(A.matrix[1][5],1);
}

int main (){
	read();
}

思路二:DP

注:转自题解

时间复杂有点高(相对于上一个来说):O(n)

我们把E去掉,这个系统分成了以A为中心的完全对称的列。

我们记dp[i][j]表示第i站在j次乘坐后到达的方案数。

因为每次只能从相邻的站到达,那么第N次到达E的方案数等于第N1次到达DF的方案数。

以此类推dp[i][j]=dp[i1][j1]+dp[i+1][j1] 这里的i1i+1$表示相邻的站。

之前说过以A为中心完全对称,所以DFCG......的方案数完全相同,只需算一边。

仔细观察,每个状态只与上一个状态有关,因此可以压位为dp[4][2]

CODE

#include<iostream>
using namespace std;
int dp[4][2];              //具体含义如上所述
int main()
{
    fill(dp[0],dp[0]+4*2,0);
    dp[0][0]=1;
    int N,pos=0;
    cni>>N;
    for(int k=1;k<N;k++)
    {
        pos=pos^1;
        dp[0][pos]=2*dp[1][pos^1]%1000;        //A处的方案等于两边的方案相加,由于相等只算一边的*2
        dp[1][pos]=(dp[0][pos^1]+dp[2][pos^1])%1000;
        dp[2][pos]=(dp[1][pos^1]+dp[3][pos^1])%1000;
        dp[3][pos]=dp[2][pos^1];                     //D只能由C来
    }
    cout<<2*dp[3][pos]%1000<<endl;            //E由D和F来
    return 0;        
}


posted @   轩Demonmaster  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示