洛谷P5241 序列

Address

洛谷P5241

Solution

  • 考虑构造出一条 n n n 个点的有向链,那么每次就可以将任意个数的点缩成一个。
  • 因此我们可以将每一个本质不同的序列 B B B 与每加一条边都进行下面的其中一个操作一一对应:
  1. 在有向链的末尾增加一个点;
  2. 将有向链的前若干个点缩成一个点;
  3. 若有向链包含的点数已经为 n n n,任加一条不会将点缩起来的边。
  • 对于其余的情形,因为在所需边数以及缩点的范围方面都不如直接构造有向链要优,可以不考虑。
  • 注意到操作 1 和操作 3 不会交替出现,因此可以将这两个过程分开 DP。
  • 对于只进行操作 1 和操作 2 的过程,设 f i , j , k ( k ≤ j ) f_{i,j,k}(k \le j) fi,j,k(kj) 表示已经加了 i i i 条边、有向链包含的点数为 j j j、前 k k k 个点已经被缩成一个点时的方案数, 易得转移:
    f i , j , k = f i − 1 , j − 1 , k + ∑ r = 1 k − 1 f i − 1 , j , r \begin{aligned} f_{i,j,k} = f_{i - 1, j - 1, k} + \sum \limits_{r = 1}^{k - 1}f_{i - 1, j,r} \end{aligned} fi,j,k=fi1,j1,k+r=1k1fi1,j,r
  • 对于只进行操作 2 和操作 3 的过程,设 g i , k g_{i,k} gi,k 表示已经加了 i i i 条边、前 k k k 个点已经被缩成一个点时的方案数,易得转移:
    g i , k = f i , n , k + ∑ r = 1 k g i − 1 , r \begin{aligned} g_{i,k} = f_{i,n,k} + \sum \limits_{r = 1}^{k}g_{i - 1,r} \end{aligned} gi,k=fi,n,k+r=1kgi1,r
  • 注意到操作 3 可以进行的次数不是无限的,因此我们需要限制状态中的 i i i 不能超过将前 k k k 个点缩成一个点时图中可能的最大边数,即:
    i ≤ k ( n − 1 ) + ( n − k ) ( n − k − 1 ) 2 \begin{aligned} i \le k(n - 1) + \dfrac{(n - k)(n - k - 1)}{2} \end{aligned} ik(n1)+2(nk)(nk1)
  • 最后加入 i i i 条边的答案为:
    a n s i = ∑ j = 1 n − 1 ∑ k = 1 j f i , j , k + ∑ k = 1 n g i , k \begin{aligned} ans_i = \sum \limits_{j = 1}^{n - 1} \sum \limits_{k = 1}^{j} f_{i,j,k} + \sum \limits_{k = 1}^{n}g_{i,k} \end{aligned} ansi=j=1n1k=1jfi,j,k+k=1ngi,k
  • 前缀和优化即可,显然 f f f 中的 i i i 只要枚举到 2 ( n − 1 ) 2(n - 1) 2(n1),总的时间复杂度 O ( n 3 ) \mathcal O(n^3) O(n3)

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	char ch;
	while (ch = getchar(), !isdigit(ch));
	res = ch ^ 48;
	while (ch = getchar(), isdigit(ch))
		res = res * 10 + ch - 48;
}

template <class T>
inline void put(T x)
{
	if (x > 9)
		put(x / 10);
	putchar(x % 10 + 48);
}

const int N = 4e2 + 5;
const int M = 16e4 + 5;
const int mod = 1e9 + 7;
int f[2][N][N], sf[2][N][N], g[M][N], ans[M], n, m;

template <class T>
inline T Min(T x, T y) {return x < y ? x : y;}

inline void add(int &x, int y)
{
	x += y;
	x >= mod ? x -= mod : 0;
}

int main()
{
	read(n);
	if (n <= 1)
		return 0;

	m = n * (n - 1);
	f[0][1][1] = 1;
	for (int k = 1; k <= n; ++k)
		sf[0][1][k] = 1;
	for (int i = 1, im = n - 1 << 1; i <= im; ++i)
	{
		int now = i & 1, lst = now ^ 1;
		for (int j = 1; j <= n; ++j)
			for (int k = 1; k <= j; ++k)
				add(f[now][j][k] = f[lst][j - 1][k], sf[lst][j][k - 1]);
		for (int k = 1; k <= n; ++k)
			add(g[i][k], f[lst][n - 1][k]);
		for (int j = 1; j <= n; ++j)
		{
			for (int k = 1; k <= n; ++k)
			{
				add(sf[now][j][k] = f[now][j][k], sf[now][j][k - 1]);
				if (j < n)
					add(ans[i], f[now][j][k]);
			}
		}
	}
	for (int i = n - 1; i <= m; ++i)
	{
		int res = 0;
		for (int j = 1; j <= n; ++j)
		{
			add(res, g[i - 1][j]);
			if (i <= j * (n - 1) + (n - j) * (n - j - 1) / 2)
				add(g[i][j], res);
			add(ans[i], g[i][j]);
		}
	}

	for (int i = 1; i <= m; ++i)
		put(ans[i]), putchar(' ');
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值