bzoj 2004: [Hnoi2010]Bus 公交线路

本文解决了一个关于公交车线路规划的问题,城市中有N个站点,需要设计线路使得每站恰好被一辆车经过,且满足特定规则。使用了矩阵优化的动态规划方法进行求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

解题报告:
用时:2h30min,1AC
这题拿着毫无办法,只能请教大佬,原来是矩阵优化DP,这个DP也是非常的诡异,首先我们要保证移动不超过P,所以我们以P为单位做矩阵,然后P很小可以状压,所以我们想办法DP,可以设\(f[i][j]\)表示\(i\)这个位置起,后面P个站的是否有车的状态,1表示有,0表示没有,转移方程类似于:
\(f[i][j]+=f[i-1][k]\) 条件是\(j\)相比\(k\)只动了一辆车的先后顺序。
但是没这么简单,因为对于\((1,3),(2,4)\)这两个状态既可以从\((1,3),(2)\)转移来,也可以从\((1),(2,4)\)转移来,所以方案会算重,所以要强制要求只有一辆车可以动,这里强制只能动 \(i\),也就是说这个例子中只有 \(1\) 可以移动,就没问题了,因为每次只移动一位且转移相同,直接丢到矩阵里做n-k次即可,然后辅助矩阵的\((i,j)\)表示i这个状态可以转移到j,所以合法的话我们就赋值为1,对于\(i\)是否能转移到\(j\)\(check\),就是相当于\(i\)整体右移一位,并且除了第P位,其他位置要相同,只有多出来的位置可以不同.

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#define RG register
#define il inline
#define iter iterator
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
using namespace std;
const int mod=30031;
int n,m,p,f[233],cnt=0;
void dfs(int dep,int tot,int sum){
    if(sum==m){
        f[++cnt]=tot;
        return ;
    }
    if(dep==p+1)return ;
    dfs(dep+1,tot+(1<<(p-dep)),sum+1);
    dfs(dep+1,tot,sum);
}
struct mat{
    int a[141][141];
    mat(){memset(a,0,sizeof(a));}
    mat operator *(const mat &pr)const{
        mat tmp;
        for(int i=1;i<=cnt;i++)
            for(int j=1;j<=cnt;j++)
                for(int k=1;k<=cnt;k++){
                    tmp.a[i][j]+=a[i][k]*pr.a[k][j]%mod;
                    if(tmp.a[i][j]>=mod)tmp.a[i][j]-=mod;
                }
        return tmp;
    }
};
bool check(int x,int y){
    int tmp=(x-(1<<(p-1)))<<1;
    y^=tmp;if(y==(y&(-y)))return true;
    return false;
}
void work()
{
    scanf("%d%d%d",&n,&m,&p);
    dfs(2,1<<(p-1),1);
    mat T;
    for(int i=1;i<=cnt;i++)
        for(int j=1;j<=cnt;j++)
            if(check(f[i],f[j]))T.a[i][j]=1;
   mat S;
    for(int i=1;i<=cnt;i++)S.a[i][i]=1;
    n-=m;
    while(n){
        if(n&1)S=S*T;
        T=T*T;n>>=1;
    }
    printf("%d\n",S.a[1][1]);
    //为什么答案是这个?因为我的dfs顺序是这样的啊=.=
}
int main()
{
    work();
    return 0;
}

转载于:https://www.cnblogs.com/Yuzao/p/7587307.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值