Codeforces 848E: Days of Floral Colours

题目传送门:CF848E

题解

我们称一对相距为 \(n\) 的同色花为一个「隔板」。

将题目中定义的连续段,向逆时针方向扩展一格,也就是也把作为隔板的花包含进去。

由于花钟是对称的,显然美丽度是完全平方数,因为一个连续段中心对称后还是一样的连续段。

长度为 \(m\) 的连续段会为答案产生 \({(m - 1)}^2\) 的乘法贡献(只计算一边)。

环上相对不好考虑,我们还是考虑破环为链,因为是对称的,我们只需考虑 \(1 \sim n\) 中的所有花。

可是 \(1\) 所在的连续段可能和 \(n\) 所在的连续段是跨过分界点相连的,这该怎么办?

我们这样考虑:对于 \(1\) 所在连续段的最「左」(想象这是上半圆弧)侧点(也就是那个隔板),它可能在 \(1\) 本身的位置,也有可能在 \(n + 2 \sim 2 n\) 之间,把它的位置强行顺时针移动到 \(1\) 的位置,这样就不会出现跨过分界点相连的情况。但是会统计漏情况,就是那些隔板不在 \(1\) 本身的位置上的情况,解决的方法是枚举这一段的长度 \(m\),那么就有 \(m\) 种旋转方案了,具体细节下面展开讨论。

总之经过转换我们可以看成是只需统计序列上而非环上的情况。

我们是以每个隔板,也就是每个连续段一起转移的,先考虑这样的简单情况:

长度为 \(m\) 的一段,只允许段内连边,且段内不能有隔板,问方案数,令其为 \(g[m]\)

段内是没有隔板的,那就只有两种最小单元的连边形式:\((i) \leftrightarrow (i + 1)\)\((i) \leftrightarrow (i + 2), (i + 1) \leftrightarrow (i + 3)\)

一个占用连续两个位置,另一个占用连续四个位置。显然:\(g[0] = 1\)\(g[m] = g[m - 2] + g[m - 4]\)

如果斐波那契数列是 \(f\),其实有 \(f[n + 1] = g[2 n]\),不过这不是关键点,只是为了帮助理解。

这个数列在一定意义下确实可以表示一个连续段了,只不过还有点瑕疵,因为连续段可不止一种。

其实有 \(4\) 种(或 \(3\) 种)连续段,根据该连续段左右侧的隔板的情况分类:

  • 左右侧隔板都没有被一对距离为 \(2\) 的同色花对跨过。
  • 左侧隔板被跨过,右侧隔板没被跨过。
  • 左侧隔板没被跨过,右侧隔板被跨过。
  • 左右侧隔板都被跨过。

中间两类是对称的所以计数的时候求出来的值肯定都相同。

\(1\) 种连续段,长度为 \(m\) 时方案数就是 \(g[m - 1]\)

\(2, 3\) 种连续段,长度为 \(m\) 时方案数就是 \(g[m - 2]\)

\(4\) 种连续段,长度为 \(m\) 时方案数就是 \(g[m - 3]\)

然而每一种对答案都产生 \({(m - 1)}^2\) 的乘法贡献。

然后就可以做 DP 了,每次转移一整个连续段。

每个连续段需要根据左右侧隔板的状态来确定方案,一个连续段的右侧隔板同时是下一个连续段的左侧隔板。

也就是说 DP 状态中记录下上个连续段的右侧隔板的状态是有必要的。

实际上记录左侧隔板的状态也是有必要的,这与我们前面提到的最后把序列上的答案转换回环上有关。

总之我们定义下列几个序列:

  • \(f_0[m]\) 表示连续段拼起来总长为 \(m\),且最左侧、最右侧隔板都没被跨过,每种方案的贡献总和。
  • \(f_1[m]\) 表示最左侧或最右侧恰有一侧的隔板被跨过的贡献总和,由对称性它们俩的值相同。
  • \(f_2[m]\) 表示最左右两侧的隔板都被跨过了的贡献总和。

那么它们就可以互相转移了:

\[\begin{aligned} f_0[m] &= {(m - 1)}^2 g[m - 1] + \sum_{i = 1}^{m - 1} f_0[m - i] {(i - 1)}^2 g[i - 1] + \sum_{i = 2}^{m - 2} f_1[m - i] {(i - 1)^2} g[i - 2] \\ f_1[m] &= {(m - 1)}^2 g[m - 2] + \sum_{i = 2}^{m - 1} f_0[m - i] {(i - 1)}^2 g[i - 2] + \sum_{i = 3}^{m - 2} f_1[m - i] {(i - 1)^2} g[i - 3] \\ f_2[m] &= {(m - 1)}^2 g[m - 3] + \sum_{i = 2}^{m - 2} f_1[m - i] {(i - 1)}^2 g[i - 2] + \sum_{i = 3}^{m - 3} f_2[m - i] {(i - 1)^2} g[i - 3] \end{aligned} \]

形式差不多,具体就是第一项是其中没有其它隔板的情况,第二项是倒数第二个隔板没被跨过的情况,第三项是倒数第二个隔板被跨过的情况。其中 \(f_1[m]\) 的递推式假设最左侧隔板没被跨过,而最右侧隔板被跨过了。

这样直接转移,复杂度是 \(\mathcal O (n^2)\) 的,如果你使用了 CDQ 多项式乘法就可以成功优化到 \(\mathcal O (n \log^2 n)\)

算出 \(f_{0, 1, 2}\) 后,最后一步就是把序列的答案还原成环了。枚举位置 \(1\) 所在的连续段的长度以及属于哪一类,剩下的就可以看作是序列了,然后套用 \(f_{0, 1, 2}\) 得到剩下部分的答案即可。

到这里应该是做完了,这里是代码。但是据说有人十分丧心病狂,令

  • \(\displaystyle G_0 = \mathbf{OGF} \left( { \left\{ {(i - 1)}^2 g[i - 1] \right\} }_{i = 1}^{\infty} \right)\)
  • \(\displaystyle G_1 = \mathbf{OGF} \left( { \left\{ {(i - 1)}^2 g[i - 2] \right\} }_{i = 2}^{\infty} \right)\)
  • \(\displaystyle G_2 = \mathbf{OGF} \left( { \left\{ {(i - 1)}^2 g[i - 3] \right\} }_{i = 3}^{\infty} \right)\)

以及 \(F_0 = \mathbf{OGF}(f_0), F_1 = \mathbf{OGF}(f_1), F_2 = \mathbf{OGF}(f_2)\)

然后把它们都算出来(用封闭形式表示),结果发现它们都是多项式除以多项式的形式,十分优美。

而且分母是低阶多项式,就可以线性求逆了,然后在 \(\mathcal O (n)\) 的时间内求出 \(f_{0, 1, 2}\),再解决序列变成环的问题。

其实可以更进一步推出最终答案的生成函数然后线性递推。不过暴力写完,整一个 BM 它不香吗?

搞了前 \(120\) 项扔进 BM 可以搞出一个 \(16\) 项线性递推式:

\[\{0, 4, 8, -1, 16, -10, 4, -12, -48, 26, -44, 15, -16, -4, -4, -1\} \]

可以得到如下代码:

#include <cstdio>
#include <algorithm>

typedef long long LL;
const int Mod = 998244353;

int N, Ans;

int main() {
	scanf("%d", &N);
	static const int R[16] = {0, 4, 8, -1, 16, -10, 4, -12, -48, 26, -44, 15, -16, -4, -4, -1};
	int A[16] = {0, 0, 0, 24, 4, 240, 204, 1316, 2988, 6720, 26200, 50248, 174280, 436904, 1140888, 3436404};
	for (int i = 16; i <= N; ++i) {
		int x = 0;
		for (int j = 0; j < 16; ++j)
			x = (x + (LL)R[(i - j - 1) & 15] * A[j]) % Mod;
		A[i & 15] = x;
	}
	printf("%d\n", (A[N & 15] + Mod) % Mod);
	return 0;
} // check 848E.cpp for CDQFFT
posted @ 2020-04-07 02:51  粉兔  阅读(579)  评论(3编辑  收藏  举报