题目链接
http://www.lydsy.com/JudgeOnline/problem.php?id=2004
思路
用
f[i][S]表示最快的公交车到了第i个公交车站,当前所有站台是否停靠公交车的状态为S,(S中第k位为1表示第k个车站上停了公交车,否则就是没停公交车)的方案数
,很容易推出DP的方程
f[i][S]=∑i−1j=kf[j][S′],S′转移到S是合法的
。
然后注意到题目中有限制:
一个公交车经过的相邻两个站台间的距离不超过p
,这个条件非常重要,可以大大减少
S
的范围——从
然后发现对于每个
f[i][S]
来说,可以拿来转移到它的(不考虑是否合法)的
S′
都是一样的,那么就可以用矩阵快速幂来优化这个DP。很显然这个DP的状态转移过程是个DAG,如下图
这样就可以把这个DP的过程看成是在每个节点代表一个状态的DAG里,用
n−k
单位时间从初始状态走到终点状态的方案数,这是个非常经典的问题,在《工程数学线性代数》课本中就有相应的例题。
最终的状态应该如下图所述,这个很显然
然后就是判断
S′是否能转移到S
的问题了,
S
中的所有1都向左移动一格,保留后
我很SB地因为模数习惯性取1e9+7贡献了一发WA,郁闷
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXN 140
#define MOD 30031
using namespace std;
struct Matrix
{
int num[MAXN][MAXN];
int n,m; //n*m大小矩阵
void setOne(int a,int b)
{
n=a,m=b;
for(int i=1;i<=min(n,m);i++) num[i][i]=1;
}
Matrix() { memset(num,0,sizeof(num)); }
}T,A,one;
Matrix operator*(Matrix a,Matrix b)
{
Matrix c;
c.n=a.n,c.m=b.m;
for(int i=1;i<=c.n;i++)
for(int j=1;j<=c.m;j++)
for(int k=1;k<=a.m;k++)
c.num[i][j]=(c.num[i][j]+a.num[i][k]*b.num[k][j])%MOD;
return c;
}
Matrix fastPow(Matrix base,int pow)
{
Matrix ans;
ans.setOne(base.n,base.m);
while(pow)
{
if(pow&1) ans=ans*base;
base=base*base;
pow>>=1;
}
return ans;
}
int calc(int x) //计算x的二进制中1的个数
{
int sum=0;
while(x)
{
sum++;
x-=x&(-x); //x去掉最后一个1
}
return sum;
}
int n,k,p,goal; //goal是目标状态
bool canConvert(int to,int from) //检查状态from能否一步转移到状态to
{
from=(from-(1<<(p-1)))<<1; //这一步相当于把from向左推了一位,个位用0补齐
int tmp=from^to; //tmp应该只有一个1
if(tmp==(tmp&(-tmp))) return true; //tmp只有一个1,则是合法的
return false; //否则是不合法的
}
int status[MAXN],top=0; //保存所有DP过程中可能出现的状态的栈
int main()
{
scanf("%d%d%d",&n,&k,&p);
for(int S=(1<<(p-1));S<(1<<p);S++) //枚举DP状态S,S是合法状态当且仅当S的二进制中1的个数恰好为k
{
if(calc(S)==k)
{
status[++top]=S;
if(S==(1<<p)-1-((1<<(p-k))-1)) goal=top; //S是最终要达到的状态
}
}
for(int i=1;i<=top;i++)
for(int j=1;j<=top;j++)
if(canConvert(status[i],status[j]))
T.num[i][j]=1;
A.n=A.m=T.n=T.m=top;
A.num[1][goal]=1;
T=fastPow(T,n-k);
A=A*T;
printf("%d\n",A.num[1][goal]);
return 0;
}