BZOJ 2004: [Hnoi2010]Bus 公交线路 [DP 状压 矩阵乘法]

传送门

题意:

$n$个公交站点,$k$辆车,$1...k$是起始站,$n-k+1..n$是终点站

每个站只能被一辆车停靠一次

每辆车相邻两个停靠位置不能超过$p$

求方案数

$n \le 10^9,\ p \le 8,\ k \le 10$



思考过程中遇到的主要问题是“所有车是同时前进的”,既不能单独考虑一辆车又没法考虑前面的车队后面的影响

正确的做法是同时考虑所有车

每$p$个位置一定每辆车各停一次

$f[i][s]$表示当前在站点$i$,且$i$有车,$s$为车停靠状态

强制规定最靠左(即$i$处)的车先走避免重复

发现状态形成一个图,建立状态之间的邻接矩阵,就可以矩乘来算了

状态最多有$\binom{9}{5}=126$种,我$dfs$状态的时候省去了强制的$1$

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N=128,MOD=30031;
inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<‘0‘||c>‘9‘){if(c==‘-‘)f=-1;c=getchar();}
    while(c>=‘0‘&&c<=‘9‘){x=x*10+c-‘0‘;c=getchar();}
    return x*f;
}
int n,k,p;
struct Matrix{
    int a[N][N];
    int* operator [](int x){return a[x];}
    Matrix(){memset(a,0,sizeof(a));}
}g;
int st[N],m;
inline void mod(int &x){if(x>=MOD) x-=MOD;}
Matrix operator *(Matrix a,Matrix b){
    Matrix re;int n=m;
    for(int k=0;k<n;k++)
        for(int i=0;i<n;i++) if(a[i][k])
            for(int j=0;j<n;j++) if(b[k][j])
                mod(re[i][j]+=a[i][k]*b[k][j]%MOD);
    return re;
}
Matrix operator ^(Matrix a,int b){
    Matrix re;int n=m;
    for(int i=0;i<n;i++) re[i][i]=1;
    for(;b;b>>=1,a=a*a)
        if(b&1) re=re*a;
    return re;
}
void dfs(int d,int num,int s){//printf("Dfs %d %d %d\n",d,num,s);
    if(num==0) {st[m++]=s;return;}
    for(int i=d;i<p-1;i++) dfs(i+1,num-1,s|(1<<i));
}
void build(){
    for(int i=0;i<m;i++)
        for(int j=0;j<m;j++){
            int s= st[i]^(st[j]>>1);//printf("build %d %d %d\n",st[i],st[j],s);
            if(s == (s&-s)) g[i][j]=1;
        }
}
int main(){
    freopen("in","r",stdin);
    n=read();k=read();p=read();
    dfs(0,k-1,0);
    //for(int i=0;i<m;i++) printf("st %d  %d\n",i,st[i]);
    build();
    //for(int i=0;i<m;i++) for(int j=0;j<m;j++) printf("%d%c",g[i][j],j==m-1?‘\n‘:‘ ‘);puts("");
    Matrix a,t=g^(n-k);

    //for(int i=0;i<m;i++) for(int j=0;j<m;j++) printf("%d%c",t[i][j],j==m-1?‘\n‘:‘ ‘);;puts("");
    a[0][0]=1;
    a=a*t;
    //for(int i=0;i<m;i++) for(int j=0;j<m;j++) printf("%d%c",a[i][j],j==m-1?‘\n‘:‘ ‘);puts("");
    printf("%d",a[0][0]);
}
时间: 2024-11-03 03:35:08

BZOJ 2004: [Hnoi2010]Bus 公交线路 [DP 状压 矩阵乘法]的相关文章

[BZOJ 2004] [Hnoi2010] Bus 公交线路 【状压DP + 矩阵乘法】

题目链接: BZOJ - 2004 题目分析 看到题目完全不会..于是立即看神犇们的题解. 由于 p<=10 ,所以想到是使用状压.将每个连续的 p 个位置压缩成一个 p 位 2 进制数,其中共有 k 位是1,表示这 k 个位置是某辆 Bus 当前停下的位置.需要注意的是,每个状态的第一位必须是 1 ,这样保证了不会有重复的状态. 每个状态可以转移到右边的某些状态(由当前状态的第一个 1 移动).初始状态和终止状态都是前面 k 位是 1 .用矩阵转移 n - k 次. 代码 #include <

【BZOJ2004】[Hnoi2010]Bus 公交线路 状压+矩阵乘法

[BZOJ2004][Hnoi2010]Bus 公交线路 Description 小Z所在的城市有N个公交车站,排列在一条长(N-1)km的直线上,从左到右依次编号为1到N,相邻公交车站间的距离均为1km. 作为公交车线路的规划者,小Z调查了市民的需求,决定按下述规则设计线路: 1.设共K辆公交车,则1到K号站作为始发站,N-K+1到N号台作为终点站. 2.每个车站必须被一辆且仅一辆公交车经过(始发站和终点站也算被经过). 3.公交车只能从编号较小的站台驶往编号较大的站台. 4.一辆公交车经过的

[BZOJ2004][Hnoi2010]Bus 公交线路

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

[HNOI2010]BUS 公交线路

题面 Bzoj Sol 状压很显然 重点在于转移:题目就相当与每\(p\)长度的车站必须有且仅有\(k\)个被经过 那么转移时状压的二进制一定要有\(k\)个一 且两个相邻转移的状态之间必须满足:设为\(i->j\),则\((i >> 1) \&j\)要有\(k-1\)个\(1\) 然后就可以加上矩阵快速幂优化,注意把满足要求的状态记下来,只有一百多个 我常数丑是我的错 # include <bits/stdc++.h> # define RG register #

BZOJ 1097: [POI2007]旅游景点atr [DP 状压 最短路]

传送门 题意: 一个无向图,从$1$到$n$,要求必须经过$2,3,...,k+1$,给出一些限制关系,要求在经过$v \le k+1$之前必须经过$u \le k+1$ 求最短路 预处理出$1...k+1$到其他点的最短路 然后$f[i][s]$表示当前在$i$已经经过的点的集合为$s$的最短路 只考虑$1,2,...,k+1$就行了, 注意$1$也要考虑,一个点可能经过多次 然后实测dij比spfa快....我想试$pb\_ds$来着结果发现我的电脑上没有ext/pb_ds/priority

HDU 4352 XHXJ&#39;s LIS 数位DP + 状压

由LIS的nlogn解法 可以得出最后统计数组中数的个数即为LIS的长度 这样就可以状压了 #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #include <climits> #include <string> #include <iostream> #include <map> #include <c

2017.8.15 [Haoi2016]字符合并 区间dp+状压dp

[题目描述] 有一个长度为n的01串,你可以每次将相邻的k个字符合并,得到一个新的字符并获得一定分数.得到的新字符和分数由这k个字符确定.你需要求出你能获得的最大分数. [输入格式] 第一行两个整数n,k. 接下来一行长度为n的01串,表示初始串.输入的的相邻字符之间用一个空格隔开. 接下来2k行,每行一个字符ci和一个整数wi,ci表示长度为k的01串连成二进制后按从小到大顺序得到的第i种合并方案得到的新字符, wi表示对应的第i种方案对应获得的分数. [输出格式] 输出一个整数表示答案. [

HDU.4352.XHXJ&#39;s LIS(数位DP 状压 LIS)

题目链接 数位DP. 至于怎么求LIS,因为只有10个数,所以可以参照O(nlogn)求LIS的方法,状压记录状态. 每次加一个数和求LIS一样更新状态.最后状态中1的个数就是LIS的长度. //93MS 3004K #include <cstdio> #include <cctype> #include <cstring> #include <algorithm> #define gc() getchar() typedef long long LL; c

『字符合并 区间dp 状压dp』

字符合并 Description 有一个长度为 n 的 01 串,你可以每次将相邻的 k 个字符合并,得到一个新的字符并获得一定分数.得到的新字符和分数由这 k 个字符确定.你需要求出你能获得的最大分数. Input Format 第一行两个整数n,k.接下来一行长度为n的01串,表示初始串. 接下来2^k行,每行一个字符ci和一个整数wi,ci表示长度为k的01串连成二进制后按从小到大顺序得到的第i种合并方案得到的新字符,wi表示对应的第i种方案对应获得的分数. 1<=n<=300,0<