截至目前该题的题解都给出了一个比较厉害的转化方式,但这篇题解介绍的是如何不通过厉害的观察做出这题。
先考虑拆分一下问题使得问题具有某些特殊性。
首先因为连边的编号差都为 \(A\) 或 \(B\),所以对于 \(i \not \equiv j\pmod {\gcd(A, B)}\),那么 \(i, j\) 的选取一定是不会互相影响的。
于是首先可以考虑按照 \(i\bmod \gcd(A, B)\) 分组,不同的组互相不干扰,方案数就可以直接相乘。
分组后的好处是处理每一组的 \(A, B\) 实际上是 \(\frac{A}{\gcd(A, B)}, \frac{B}{\gcd(A, B)}\)。
也就是说此时 \(\gcd(A, B) = 1\),每一组的每一个点一定都是有用的。
接下来考虑如何处理这个问题。
首先有一个非常暴力的做法。
考虑每一个匹配都由右端点来决定,那么对于 \(i\) 来说实际上只关心 \(i - A\) 和 \(i - B\) 能否匹配上。
那么对于 \(j < i - B\) 来说一定就不在 \(i\) 的考虑范围内,因为不管是 \(i\) 还是 \(i' > i\),\(j\) 都是没用的。
于是只需要考虑 \([i - B, i)\) 这 \(B\) 个位置是否已经被匹配上,每次加入 \(i\) 时尝试与 \(i - A, i - B\) 配对并更新。
直接把 \(B\) 个位置的信息状压 dp 就可以做到 \(\mathcal{O}(n2^{B})\)。
上面一个做法的坏处是在 \(B\) 太大的时候复杂度太劣。
于是这启发去尝试一些 \(B\) 比较大的时候的做法。
因为这题看着维护的信息就比较多,所以这部分的做法可能依然是个状压。
于是就要尝试让状压的幂次带有 \(\frac{1}{B}\) 状物。
于是可以尝试 \(\frac{n}{B}\),考虑这个的实际意义是在 \(n\) 个位置中按 \(B\) 个分段,段数就为 \(\frac{n}{B}\)。
进一步的,因为关心的是位置,可以考虑把这个 \(\frac{n}{B}\) 当作从每一段中选一个位置,选出的的位置数是 \(\frac{n}{B}\) 的。
再进一步,按 \(B\) 个分段给出了一个很好的性质:如果只考虑编号差为 \(B\) 的边,那么有可能有连边的点编号一定 \(\bmod B\) 相同。
结合上面的想法,就可以把每一段中编号 \(\bmod B\) 的点放在一起状压。
因为此时状态里的点就是编号差为 \(B\) 的点,于是对于编号差为 \(B\) 的连边就很好做了。
具体来说,可以直接考虑状态里 \(i\) 和 \(i + B\) 的点要不要匹配上并更新状态。
需要注意的是,可能会在过程中因为多种顺序而记重,解决的方式是类似高位前缀和,外层枚举 \(i\) 内层枚举状态转移。
接下来就考虑一个新的问题:如何转移编号差为 \(A\) 的边。
首先因为状态中的点 \(\bmod B\) 都相等,设为 \(i\)。
那么接下来转移编号差为 \(A\) 的边,就可以考虑直接从 \(\bmod B = i\) 跳到 \(= (A + i) \bmod B\) 的点集,此时因为 \(\gcd(A, B) = 1\),所以保证了每条编号差为 \(A\) 的边都能够被跳到。
接下来考虑对于状态 \(f_s\) 记录的应该是点集里的点有无被匹配上的方案数。
那么考虑这个 \(s\) 中的点 \(x\bmod B = i\) 要满足什么才能如何匹配上 \(x + A\):
- \(x\) 与 \(x + A\) 有连边。
- \(x\) 应该在 \(s\) 中属于为匹配的一类。
于是首先对于 \(s\) 中已经匹配上的点以及不存在与后面 \(+A\) 的连边的点肯定无法产生匹配。
那么剩下的点就是可以与后面的点产生匹配的点,把点集记为 \(t\)。
那么对于一个点集 \(p\subseteq t\),就可以把 \(p\) 中的点与后面的点匹配,那么对于 \((i + A)\bmod B\) 的状态就是 \(p\) 中的点已经匹配上了,而不在 \(p\) 中的点都没有匹配。
于是所有 \(p\subseteq t\) 都可以得到 \(f_s\) 的方案数,那么这可以写成一个高位后缀和的形式,用 FMT-and 即可。
但是此时还有一点问题,就是 \(-A\bmod B\) 和 \(0\) 之间的匹配问题。
因为每次 dp 都是考虑的从 \(i\) 跳到 \((i + A)\bmod B\),但是这个跳的是一个环,对于最后跳的环边有可能并不满足起始状态的限制。
对此可以直接暴力枚举这条换边的情况,也就是暴力枚举 \(-A\bmod B\) 和 \(0\) 直接匹配的情况,如果合法再得到初始状态 dp。
于是就可以在 \(\mathcal{O}(B\times \frac{n}{B}\times 2^{2\frac{n}{B}}) = \mathcal{O}(n2^{2\frac{n}{B}})\) 的复杂度解决 \(B\) 较大的时候。
根据 \(B\) 的大小选择做法,平衡两个做法的复杂度,可以做到 \(\mathcal{O}(n2^{\sqrt{2n}})\)。
实际写的时候可以直接当 \(B\le 20\) 用做法 1 否则用做法 2。
#include<bits/stdc++.h>
using ll = long long;
constexpr ll mod = 998244353;
inline void add(ll &x, ll y) { x = (x + y) % mod; }
constexpr int maxn = 6e2 + 10;
inline ll calc(int n, int A, int B, const auto &G) {if (B <= 20) {static ll f[1 << 20], g[1 << 20];memset(f, 0, sizeof(f)), f[0] = 1ll;const int mask = (1 << B) - 1;for (int i = 0; i < n; i++) {memcpy(g, f, sizeof(g)), memset(f, 0, sizeof(f));for (int s = 0; s <= mask; s++) {add(f[(s << 1) & mask], g[s]);if (i >= A && G[i - A][i] && (~ s >> A - 1 & 1)) {add(f[(s << 1 | 1 << A | 1) & mask], g[s]);}if (i >= B && G[i - B][i] && (~ s >> B - 1 & 1)) {add(f[(s << 1 | 1 << B | 1) & mask], g[s]);}}}ll ans = 0ll;for (int s = 0; s <= mask; s++) add(ans, f[s]);return ans;} else {static ll f[1 << 10], g[1 << 10];n = ((n - 1) / B + 1) * B;const int D = n / B;const int mask = (1 << D) - 1;ll ans = 0ll;for (int U = 0; U <= mask; U += 2) {bool chk = true;for (int i = 0; i < D; i++) {if (U >> i & 1) {chk &= G[i * B - A][i * B];}}if (chk == false) continue;memset(f, 0, sizeof(f)), f[U] = 1;for (int w = 0; ; w = (w + A) % B) {for (int i = 1; i < D; i++) {for (int s = 0; s <= mask; s++) {if ((~ s >> i & 1) && (~ s >> i - 1 & 1) && G[(i - 1) * B + w][i * B + w]) {add(f[s | 1 << i | 1 << i - 1], f[s]);}}}int lw = w + A >= B, H = 0;for (int i = 0; i < D; i++) {if (G[i * B + w][i * B + w + A]) {H |= 1 << i + lw;}}memset(g, 0, sizeof(g));for (int s = 0; s <= mask; s++) {add(g[((mask ^ s) << lw) & H], f[s]);}for (int i = 0; i < D; i++) {if (~ H >> i & 1) continue;for (int s = 0; s <= mask; s++) {if (s >> i & 1) {add(g[s ^ (1 << i)], g[s]);}}}memcpy(f, g, sizeof(f));if (w == B - A) break;}ans = (ans + f[U]) % mod;}return ans;}assert(false);
}
int N[maxn], G[maxn][maxn][maxn];
int main() {int n, m, A, B;scanf("%d%d%d%d", &n, &m, &A, &B);int g = std::__gcd(A, B);A /= g, B /= g;for (int i = 0; i < n; i++) N[i % g]++;for (int i = 1, x, y; i <= m; i++) {scanf("%d%d", &x, &y), x--, y--;int id = x % g; assert(x % g == y % g);G[id][x / g][y / g] = G[id][y / g][x / g] = 1;}ll ans = 1ll;for (int i = 0; i < g; i++) {ans = ans * calc(N[i], A, B, G[i]) % mod;}printf("%lld\n", ans);return 0;
}