*题解:LOJ#6069. 「2017 山东一轮集训 Day4」塔
解析
考虑最终建造方案中,表示每座塔相对顺序的排列。有了这个排列,我们就可以得知在该顺序下,建塔所需的最短长度 \(k\),这样的话还剩 \(l - k\) 个空位,把这些空位插进去,方案数为 \(\binom{l - k + n}{n}\)。
故设 \(f_k\) 表示当前最短长度为 \(k\) 的建塔方案数。按高度从小到大建塔,当前已经建了的塔会在最终排列中以连续段的方式出现,我们通过构建连续段来考虑所有排列。具体地,每加入一座塔 \(i\),这个塔可能:
- 新开一个连续段。对 \(k\) 无影响。
- 接在一个连续段的左侧。由于新加的塔是最高的,故这个塔与其右侧的塔距离为 \(i\),\(k\leftarrow k + i\)。
- 接在一个连续段的右侧。同理,\(k\leftarrow k + i\)。
- 连接两个连续段。这个塔与两边距离都为 \(i\),\(k \leftarrow k + 2i\)。
可以发现,每一步构建的方案数还跟连续段个数有关,故还需维护一个连续段个数,最终状态为 \(f_{i,j}\) 表示当前连续段个数为 \(i\),最短长度为 \(j\) 的方案数,转移方程见代码。
组合数需要矩阵加速来求。
时间复杂度 \(O(n ^ 4)\)。
总结
尝试把构造排列转化为构造连续段。
代码
#include<bits/stdc++.h>
#define ls(x) ((x) << 1)
#define rs(x) (((x) << 1) | 1)
#define mid ((l + r) >> 1)
using namespace std;
typedef long long ll;
const int N = 100 + 5;
int f[2][N][N * N * 2];
int n,l,m;
struct Mtx{
int a[N][N],row,col;
Mtx (int r,int c){
row = r,col = c;
for(int i=0;i<r;i++){
for(int j=0;j<c;j++){
a[i][j] = 0;
}
}
}
friend Mtx operator * (Mtx &a,Mtx &b){
Mtx c(a.row,b.col);
for(int i=0;i<a.row;i++){
for(int j=0;j<b.col;j++){
for(int k=0;k<a.col;k++){
c.a[i][j] = (c.a[i][j] + 1ll * a.a[i][k] * b.a[k][j]) % m;
}
}
}
return c;
}
}ori(101,1),base(101,101);
Mtx qmi(Mtx a,int b){
Mtx res(a.row,a.col);
for(int i=0;i<a.row;i++){
res.a[i][i] = 1;
}
if(b < 0) return res;
while(b){
if(b & 1) res = res * a;
b >>= 1;
a = a * a;
}
return res;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
ori.a[0][0] = 1;
for(int i=0;i<=100;i++){
base.a[i][i] = 1;
if(i) base.a[i][i - 1] = 1;
}
cin>>n>>l>>m;
f[1][1][1] = 1;
Mtx mi = qmi(base,l - n * n + n);
Mtx now = mi * ori;
int t = 1;
for(int k=1;k<n;k++){
int x = (k & 1) ^ 1;
for(int i=1;i<=k;i++){
for(int j=1;j<=t;j++){
//新开一个连续段,有 i + 1 个位置可选
f[x][i + 1][j] = (f[x][i + 1][j] + 1ll * f[x ^ 1][i][j] * (i + 1)) % m;
//接左侧
f[x][i][j + k + 1] = (f[x][i][j + k + 1] + 1ll * f[x ^ 1][i][j] * i) % m;
//接右侧
f[x][i][j + k + 1] = (f[x][i][j + k + 1] + 1ll * f[x ^ 1][i][j] * i) % m;
//接两侧,排除掉最左连续段的左边,最右连续段的右边
if(i >= 2){
f[x][i - 1][j + 2 * k + 2] = (f[x][i - 1][j + 2 * k + 2] + 1ll * f[x ^ 1][i][j] * (i - 1)) % m;
}
}
}
t += 2 * k + 2;
memset(f[x ^ 1],0,sizeof(f[x ^ 1]));
}
int res = 0;
for(int i=n * n;i>=n;i--){
if(l - i + n < 0){
continue;
}
if(i <= l)
res = (res + 1ll * f[n & 1][1][i] * now.a[n][0]) % m;
now = base * now;
}
cout<<res;
return 0;
}

浙公网安备 33010602011771号