题解 P7024 【[NWRRC2017]Fygon 2.0】

· · 题解

首先介绍如何对 DAG 求拓扑序的个数。

下面回到本题: > (**2021集训队作业 P7024 【[NWRRC2017]Fygon 2.0】**)给定 $m$ 层嵌套的 `for` 循环语句,每个循环包含循环变量和上下界,其中上下界是外层变量或 $1$ 和 $n$,求关于 $n$ 的渐进复杂度和最高次项系数(常数)。最内层是一个语句 `lag`,其复杂度被认为是 $O(1)$。 对所有涉及到的变量和上下界都建立结点(整数变量) $\{u_k\}$,将一个 `for ui in range(uj,uk)` 的循环视为两个整数不等式 $u_j\le u_i\le u_k$。则 `lag` 的执行次数就等于该不等式组的正整数解数。 考虑建立图论模型,若 `for` 循环中有 $u_i\le u_j$ 的约束(若其中有 $1$ 或 $n$,视为没有约束),则建边 $u_i\rightarrow u_j$。对该图进行缩点,形成一个由 SCC(强连通分量) 组成的 DAG。易见每个 SCC 中所有点的取值都是相同的,不妨设为 $v_1,v_2,...,v_k$(一共有 k 个 SCC)。我们只要求出了该 DAG 的一个拓扑序,则从小到大随意给 $v_1,...,v_k$ 赋值为 $[1..n]$ 中的互不相同的整数,便得到了该拓扑序对应的 $\binom nk$ 组解,于是总执行次数就是 $N\binom nk$,其中 $N$ 是拓扑序个数。从而渐进复杂度就是 $O(n^k)$。常数项为 $\lim_{n\rightarrow\infty}N\binom nk/n^k=N/k!$。 这里 $N$ 就可以用上述的方法,状压 DP 求出。 ```cpp #define ctz __builtin_ctz const int S = 1048586; int m, tab[128], tot; ll f[S]; inline int newnode(int x) { return (x == '0' || x == 'n' ? 0 : tab[x] ? tab[x] : tab[x] = ++tot) - 1; } int eg[21], eg_[S], dfn[21], low[21], tim, ins, cnt = -1, rt[21]; stack<int>stk; void tarjan(int x) { dfn[x] = low[x] = ++tim, stk.push(x), ins |= 1 << x; for (int v, i = eg[x]; i; i &= i - 1) if (!dfn[v = ctz(i)]) tarjan(v), low[x] = min(low[x], low[v]); else if (ins >> v & 1) low[x] = min(low[x], dfn[v]); if (dfn[x] == low[x]) { ++cnt; int y; do y = stk.top(), rt[y] = cnt, ins &= ~(1 << y), stk.pop(); while (y != x); } } int main() { freopen("1.in", "r", stdin); m = rd(); for (int i = 1; i <= m; ++i) { char ch = gtc(); while (ch != 'f' && ch != 'l') ch = gtc(); if (ch == 'f') { gtc(); gtc(); int x = gtc(); gtc(); gtc(); gtc(); gtc(); gtc(); gtc(); gtc(); gtc(); int l = gtc(); gtc(); int r = gtc(); x = newnode(x), l = newnode(l), r = newnode(r); if (~l) eg[l] |= 1 << x; if (~r) eg[x] |= 1 << r; } } for (int i = 0; i < tot; ++i) if (!dfn[i]) tarjan(i); for (int i = 0; i < tot; ++i) for (int j = eg[i], v; j; j &= j - 1) if (rt[i] != rt[v = ctz(j)]) eg_[rt[i]] |= 1 << rt[v]; ll fac = 1; for (int i = 2; i <= cnt; ++i) fac *= i; f[0] = 1; for (int i = 1, all = ~(-1 << cnt); i <= all; ++i) for (int j = i, v; j; j &= j - 1) if (!(eg_[v = ctz(j)] & i)) f[i] += f[i & ~(1 << v)]; ll fs = f[~(-1 << cnt)], g = __gcd(fs, fac); printf("%d %lld/%lld", cnt, fs / g, fac / g); return 0; } ```