题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2004
好美妙的矩阵乘。
思考:
0.在一个序列上。所以考虑dp。
1.p<=10,k<=8,所以考虑状压,1表示这一站正有公交车。
2.n<=1e9,考虑矩阵乘优化dp。
3.因为每一步<=p,所以维护长度为p的区间中的当前状态就行了,其他位置肯定全是0。这样也压得下。
实现:
为了不重不漏,需要指定一个顺序一样的东西。
比如我们可以指定最左边一定得有一辆车,而且每次状态改变一定是这辆车走了。
转移条件就是(当前状态)<<1 与 (目标状态) 只有一位不同,即( (当前状态)<<1 & (目标状态) )==k-1。
对称地,我们也可以指定最右边一定得有一辆车,状态改变时是随便位置的一辆车走到了这个位置。
转移条件略。
因为只能从左往右走,所以这两种方式比指定中间某个位置一定得有一辆车更优。
更妙的是因为每次移动区间之前都有一个 不与之前重复的 指定位置一定有车,而本次更新出来的状态都是建立在该位置有车的前提上的,所以就能保证每一站都被停过!
每一站都只停一次就是通过区间内有k个1来保证。
最后需要乘一下最初的状态,就是开头有k个1的那个。
如果我们把开始的右端看作第k个位置,结束就是在第n个位置;而且这样开始和结束的状态就都是000001111111的样子。
如果从1~(1<<p-1)枚举数字是否合法,则第一个得到的合法状态就是000001111111的样子。
所以最后乘一个只有[1][1]是1的状态矩阵(初始),再输出[1][1]位置的值(最终)就行啦!因为乘状态矩阵对[1][1]的值无影响,所以不乘也行。
代码:
合法状态不用算末位是0但有k个1的,因为没人转移给它们值,所以它们就没用了。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=155,mod=30031; int n,m,p,cnt,lm,list[N]; struct Matrix{ int v[N][N]; void init() { for(int i=1;i<=cnt;i++)v[i][i]=1; } Matrix operator *(const Matrix &b)const { Matrix tp;memset(tp.v,0,sizeof tp.v); for(int i=1;i<=cnt;i++) for(int j=1;j<=cnt;j++) for(int k=1;k<=cnt;k++) (tp.v[i][j]+=v[i][k]*b.v[k][j])%=mod; return tp; } }res,tp; int num(int x)//很好 { int ct=0; while(x){x-=(x&-x);ct++;} return ct; } int main() { scanf("%d%d%d",&n,&m,&p); lm=(1<<p); for(int i=1;i<lm;i++) if((i&1)&&num(i)==m)list[++cnt]=i; for(int i=1;i<=cnt;i++) for(int j=1;j<=cnt;j++) if(num(list[i]&(list[j]<<1))==m-1)tp.v[i][j]=1; int ct=n-m;res.init(); while(ct) { if(ct&1)res=res*tp; tp=tp*tp;ct>>=1; } printf("%d",res.v[1][1]); return 0; }