*题解: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;
}
posted @ 2026-10-14 22:26  yutar  阅读(19)  评论(2)    收藏  举报