有同学让我造福人类,所以来写一篇。
考虑显然没有什么通项公式可以利用的,但是注意到 \(m\) 仅仅只有小小的 \(6\),考虑状压 \(dp\) 的思路。设 \(dp_{i,j}\) 表示当前已经排了 \(i\) 列,状态为 \(j\) 的方案数,其中 \(1\) 表示该位置是一个跨了 \(i,i+1\) 两行的砖头,反之为 \(0\)。再设 \(g_{i,j}\) 表示 \(j\) 状态能否成为 \(i\) 状态的后继状态,则有:
\[dp_{i,t}=\sum_{g_{s,t}=1}dp_{i-1,s}
\]
考虑 \(g_{s,t}\) 何时为 \(0\):
- 当 \(s\wedge t\ne 0\) 时,出现重叠,为 \(0\);
- 当有一个极长的满足连续每个位置都有 \(((s\vee t)>>i)\wedge 1=0\) 的连续段满足长度为奇数时,出现重叠,为 \(0\)。
其余情况为 \(1\)。显然这个公式可以进行矩阵快速幂,将时间优化到 \(O((2^m+1)^3len)\)。加一是因为矩阵里要留一维存 \(s=0\) 的前缀和。
考虑这样肯定是会 \(T\) 的,但是我们可以发现,像 \(010101\) 这样的状态肯定是不会被便利到的,所以只需要统计所有能被遍历到的状态即可。看起来很少,但实际上可以把状态数从 \(2^m\le 64\) 变成 \(\dbinom{m}{\lfloor\frac m2\rfloor}\le 20\),时间复杂度暴减。
最终时间复杂度 \(O(\dbinom{m}{\lfloor\frac m2\rfloor}^3len)\)。可以通过本题。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int t;ll l,r,k,z[30];
unordered_map<ll,int>f[105];
inline ll dp(int x,ll y){if(f[x].count(y)) return f[x][y];if(!y||y<x) return 0;ll mx=0;while(z[mx+1]<=y) mx++;mx=z[mx];if(x==1) return f[x][y]=mx+1;return f[x][y]=dp(x-1,y-mx)+dp(x,mx-1);
}inline ll num(ll x,ll k){if(k>100) return 0;return dp(k,x);
}int main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0),cin>>t,f[1][z[0]=1]=;for(int i=1;i<30;i++) z[i]=z[i-1]<<2|1;while(t--){cin>>l>>r>>k;cout<<num(r,k)-num(l-1,k)<<"\n";}return 0;
}