LOJ#6070. 「2017 山东一轮集训 Day4」基因 解题报告

LOJ#6070. 「2017 山东一轮集训 Day4」基因 解题报告

塔从左到右,编号形成一个 1 ∼ n 1\sim n 1n 的排列 p i p_i pi。于是有不等式:

∀ i ∈ [ 2 , n ] , x i − x i − 1 ≥ max ⁡ ( p i − 1 , p i ) \forall i\in[2,n],x_i-x_{i-1}\ge \max(p_{i-1},p_i) i[2,n],xixi1max(pi1,pi)

设区间 [ 1 , x 1 ] [1,x_1] [1,x1] 的长度为 y 1 y_1 y1,区间 [ x i − 1 + 1 , x i ] [x_{i-1}+1,x_i] [xi1+1,xi] 的长度为 y i ( 2 ≤ i ≤ n ) y_i(2\le i\le n) yi(2in),区间 [ x n + 1 , l ] [x_n+1,l] [xn+1,l] 的长度为 y n + 1 y_{n+1} yn+1,则有

y 1 + y 2 + ⋯ + y n + 1 = l y 1 ≥ 1 , y n + 1 ≥ 0 y i ≥ max ⁡ ( p i − 1 , p i ) , 2 ≤ i ≤ n \begin{gathered} y_1+y_2+\cdots+y_{n+1}=l \\ y_1\ge 1,y_{n+1}\ge 0 \\ y_i\ge \max(p_{i-1},p_i),2\le i\le n \end{gathered} y1+y2++yn+1=ly11,yn+10yimax(pi1,pi),2in

s = ∑ i = 2 n max ⁡ ( p i − 1 , p i ) s=\sum_{i=2}^n\max(p_{i-1},p_i) s=i=2nmax(pi1,pi),则不定方程解的个数为

( l − s − 1 + n n ) \binom{l-s-1+n}{n} (nls1+n)

只与 s s s 有关。接下来的问题是,有多少这样的排列 p p p 使得 ∑ i = 2 n max ⁡ ( p i − 1 , p i ) = s \sum_{i=2}^n\max(p_{i-1},p_i)=s i=2nmax(pi1,pi)=s

考虑 dp。从小到大考虑 [ 1 , n ] [1,n] [1,n] 中的每个数将它们往排列上去填。设 f ( i , j , k ) f(i,j,k) f(i,j,k) 表示填了 [ 1 , n ] [1,n] [1,n],当前 s = j s=j s=j,排列分成了 k k k 个连通块的排列数量。那么讨论 i i i 的位置:

  1. i i i 独自形成了一个新的连通块,对 s s s 没有贡献,

f ( i , j , k ) ← k ⋅ f ( i − 1 , j , k − 1 ) f(i,j,k)\gets k\cdot f(i-1,j,k-1) f(i,j,k)kf(i1,j,k1)

  1. i i i 依附在某个连通块的左/右,对 s s s 有一次贡献,

f ( i , j , k ) ← 2 k ⋅ f ( i − 1 , j − i , k ) f(i,j,k)\gets 2k\cdot f(i-1,j-i,k) f(i,j,k)2kf(i1,ji,k)

  1. i i i 连接了两个连通块,对 s s s 有两次贡献,

f ( i , j , k ) ← k ⋅ f ( i − 1 , j − 2 i , k + 1 ) f(i,j,k)\gets k\cdot f(i-1,j-2i,k+1) f(i,j,k)kf(i1,j2i,k+1)

s = j s=j s=j 的上界大概是 n ( n + 1 ) n(n+1) n(n+1),时间复杂度 O ( n 4 ) O(n^4) O(n4)

接下来问题变为,如何求

( l − s − 1 + n n ) \binom{l-s-1+n}{n} (nls1+n)

由于 m m m 不一定小,也不一定是质数,所以它并不好算。注意到 n n n 特别小,我们可以矩阵快速幂 f 0 ⋯ n f_{0\cdots n} f0n,求出 L L L 行的 0 ⋯ n 0\cdots n 0n。然后,再进行递推即可。这部分时间复杂度 O ( n 3 log ⁡ l + n 3 ) O(n^3\log l + n^3) O(n3logl+n3)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
ll read() {
	ll x = 0, f = 1; char ch = getchar();
	for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
	return x * f;
}
const int MAXN = 105;
int P=1;
struct mint {
	int v;
	mint(int v = 0) :v(v) {}
};
int MOD(int v) {return v >= P ? v - P : v;}
mint tran(int v) {return MOD(v % P + P);}
mint operator + (mint a, mint b) {return MOD(a.v + b.v);}
mint operator += (mint& a, mint b) {return a = a + b;}
mint operator - (mint a, mint b) {return MOD(a.v - b.v + P);}
mint operator -= (mint& a, mint b) {return a = a - b;}
mint operator * (mint a, mint b) {return 1ll * a.v * b.v % P;}
mint operator *= (mint a, mint b) {return a = a * b;}
mint qpow(mint a, int n) {mint ret = 1; for(; n; n >>= 1, a = a * a) if(n & 1) ret = ret * a; return ret;}
int n, l, lim;
struct Mat {
	mint a[MAXN][MAXN];
	void clear() {
		for(int i = 0; i <= n; i++) for(int j = 0; j <= n; j++) a[i][j] = 0;
	}
	void setu() {
		for(int i = 0; i <= n; i++) for(int j = 0; j <= n; j++) a[i][j] = (i == j);
	}
	mint* operator [] (int k) {return a[k];}
	const mint* operator [] (int k) const {return a[k];}
};
Mat operator * (const Mat& a, const Mat& b) {
	Mat c; c.clear();
	for(int i = 0; i <= n; i++)
		for(int k = 0; k <= n; k++)
			for(int j = 0; j <= n; j++)
				c[i][j] += a[i][k] * b[k][j];
	return c;
}
Mat qpow(Mat a, int n) {Mat ret; ret.setu(); for(; n; n >>= 1, a = a * a) if(n & 1) ret = ret * a; return ret;}


mint f[2][MAXN * MAXN][MAXN];
mint C[MAXN * MAXN][MAXN];
int main() {
	n = read(), l = read(), P = read();
	lim = 0;
	f[0][0][0] = 1;
	for(int i = 1; i <= n; i++) {
		int p = i & 1, pre = p ^ 1;
		for(int j = 0; j <= lim; j++)
			for(int k = 0; k <= n; k++) {
				f[p][j][k] = 0;
				if(k >= 1) f[p][j][k] += k * f[pre][j][k-1];
				if(j >= i) f[p][j][k] += 2 * k * f[pre][j-i][k];
				if(j >= 2 * i) f[p][j][k] += k * f[pre][j-2*i][k+1];
			}
		lim += 2 * i;
	}
	lim = min(lim, l - 1);
	Mat T; T.clear();
	for(int i = 0; i <= n; i++) {
		T[i][i] = 1;
		if(i > 0) T[i][i-1] = 1;
	}
	T = qpow(T, l - 1 + n - lim);
	for(int i = 0; i <= n; i++) C[0][i] = T[i][0];
	for(int i = 1; i <= lim; i++) {
		C[i][0] = 1;
		for(int j = 1; j <= n; j++) C[i][j] = C[i-1][j] + C[i-1][j-1];
	}
	mint ans = 0;
	for(int i = 0; i <= lim; i++)
		ans += C[i][n] * f[n & 1][lim - i][1];
	printf("%d\n", ans.v);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日居月诸Rijuyuezhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值