发现有互不相等的限制,那就考虑一下连续段 DP。每次从小到大考虑每个数是否填,填的话填到哪里即可。
容易发现题目中的限制相当于要求每一个连续段的右边填的数不能与它差出 \(m\),且容易发现每个段的差的要求一定不相等,那么我们可以直接 \(2^m\) 状压记录每个连续段的差值要求。然后再记录一下是否已经确定了一段放在序列结尾即可。然后就是普通的连续段 DP 转移了。注意到我们这样就不需要记录连续段数量了,因为可以直接根据状态推出来。然后还得记录一下现在填了多少数,这样状态数是 \(O(k 2^{m+1})\) 的。需要注意的是我们合并的时候再确定每一段之间的位置关系,新建段的时候不考虑它的位置。(要不然还得记录最右面一段是啥,要不然没法转移)
\(n\) 很大,但是状态数不是很多,所以考虑直接矩阵快速幂优化。状态数其实还是有点多的,直接做不知道能不能跑过去。可以去掉一些无用状态,只要当前的状态不可能最后只剩下一段就直接不记录这个状态了。这样状态数有 \(367\) 个,可以跑。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 420, P = 1000000007;
int n, k, m;
int f[15][1 << 4][2];
int tot;
struct Matrix {
int a[MAXN][MAXN];
int* operator[](int b) { return a[b]; }
const int* operator[](const int b) const { return a[b]; }
Matrix() { memset(a, 0, sizeof a); }
Matrix operator*(const Matrix &b) const {
Matrix c;
for (int k = 1; k <= tot; k++) {
for (int i = 1; i <= tot; i++) {
for (int j = 1; j <= tot; j++) {
c[i][j] = (c[i][j] + 1ll * a[i][k] * b[k][j]) % P;
}
}
}
return c;
}
Matrix pow(int b) {
Matrix a = *this, ans;
for (int i = 1; i <= tot; i++) ans[i][i] = 1;
while (b) {
if (b & 1) ans = ans * a;
a = a * a;
b >>= 1;
}
return ans;
}
};
int main() {
scanf("%d%d%d", &n, &k, &m);
for (int i = 0; i <= k; i++) {
for (int s = 0; s < (1 << m); s++) {
for (int p = 0; p < 2; p++) if (i + __builtin_popcount(s) + p - 1 <= k) {
f[i][s][p] = ++tot;
// printf("f[%d][%d][%d] : %d\n", i, s, p, tot);
}
}
}
cerr << tot << endl;
Matrix a;
for (int i = 0; i <= k; i++) {
for (int s = 0; s < (1 << m); s++) {
for (int p = 0; p < 2; p++) if (i + __builtin_popcount(s) + p - 1 <= k) {
int c = __builtin_popcount(s) + p;
if (!(s >> (m - 1) & 1)) {
// do nothing
a[f[i][s][p]][f[i][s << 1][p]]++;
// new segment
a[f[i][s][p]][f[i + 1][s << 1 | 1][p]]++;
if (!p) a[f[i][s][p]][f[i + 1][s << 1][1]]++;
// append left
a[f[i][s][p]][f[i + 1][s << 1][p]] += c;
}
// append right
for (int j = 0; j < m; j++) if (s >> j & 1) {
if (((s ^ (1 << j)) << 1) >> m & 1) continue;
a[f[i][s][p]][f[i + 1][(s ^ (1 << j)) << 1 | 1][p]]++;
if (!p) a[f[i][s][p]][f[i + 1][(s ^ (1 << j)) << 1][1]]++;
}
// merge two
for (int x = 0; x < m; x++) if (s >> x & 1) {
if (((s ^ (1 << x)) << 1) >> m & 1) continue;
for (int y = 0; y < m; y++) if (x != y && (s >> y & 1)) {
a[f[i][s][p]][f[i + 1][(s ^ (1 << x)) << 1][p]]++;
}
}
if (p) {
for (int x = 0; x < m; x++) if (s >> x & 1) {
if (((s ^ (1 << x)) << 1) >> m & 1) continue;
a[f[i][s][p]][f[i + 1][(s ^ (1 << x)) << 1][p]]++;
}
}
}
}
}
auto x = a.pow(n);
printf("%d\n", x[f[0][0][0]][f[k][0][1]]);
return 0;
}
标签:状态,continue,记录,Rules,CF1152F2,int,Version,连续,一段
From: https://www.cnblogs.com/apjifengc/p/17448091.html