CF1466H Finding satisfactory solutions
题意简述
有 \(n\) 个 \(1\) 到 \(n\) 的排列,第 \(i\) 个排列为 \(P_i\)。
定义一个排列 \(A\) 是最优的,当且仅当不存在任意一个排列 \(B\) 满足存在一个下标集合 \(S\) 符合以下所有条件:
(1)\(\forall i\in S\),\(B_i\in S\)。
(2)\(\forall i\in S\),在 \(P_i\) 中 \(A_i\) 不能出现在 \(B_i\) 的左边。
(3)\(\exists i\in S\),在 \(P_i\) 中 \(B_i\) 出现在 \(A_i\) 的左边。
给定一个排列 \(A\),求有多少种 \(P_{1\dots n}\) 使得排列 \(A\) 是最优的,对 \(10^9+7\) 取模。
\(1\le n\le 40\)。
Solution
显然在 \(P_i\) 中只有出现在 \(A_i\) 及之前的数是有用的。
考虑建一张有向图:如果在 \(P_i\) 中 \(j\) 出现的位置严格在 \(A_i\) 出现的位置左边,则连白边 \((i,j)\),然后对于所有的 \(i\) 连黑边 \((i,A_i)\)。
对应到排列 \(A\) 不是最优的条件,相当于可以选出若干个不相交的环(符合了条件 \(1\)),满足这些环的边不全为黑(符合了条件 \(3\))。
到这里可以发现其实没有必要选若干个不相交的环,只要能选一个不全为黑边的环出来,\(A\) 就不是最优的了。
考虑一个强连通分量,不难发现只要分量内有白边,就一定能选出一个不全为黑边的环出来。
于是我们得到 \(A\) 是最优的条件:图的每个强连通分量内都只有黑边。
由于所有的黑边 \((i,A_i)\) 已经给出,并且所有黑边构成了若干个不相交的环,故把这些环当成一个大点,要求的就是把这些大点连成一个 DAG
的方案数。
同时由于 \(P_i\) 是排列,故如果 \(i\) 点出发的白边有 \(d\) 条,则会带上 \(d!(n-d-1)!\) 的乘积贡献(把 \(P_i\) 中 \(A_i\) 之前和之后的数安排顺序)。
考虑暴力状压 DP:\(f_S\) 表示把集合 \(S\) 内的大点连成 DAG
的方案数。
转移考虑枚举入度为 \(0\) 的点集 \(T\),加上点集 \(T\) 连向点集 \(S-T\) 的方案数乘上 \(f_{S-T}\)。
这样不能保证 \(S-T\) 内没有入度为 \(0\) 的点,故还要套一个容斥:设上面算出的结果为 \(g_T\),则真正入度为 \(0\) 的点集恰好为 \(T\) 的方案数应为 \(\sum_{T\subseteq U\subseteq S}(-1)^{|U|-|T|}g_U\)。
于是:
这样就可以枚举子集进行暴力 DP 了,复杂度 \(O(2^n)\)。
而本题中我们可以把 \(S\) 记录成每种大小的大点分别出现的次数,枚举子集可以改成枚举 \(U\) 中每种大小的大点出现了多少次后乘一个组合数,这样处理之后复杂度可以通过。
Code
#include <bits/stdc++.h>
#define mp std::make_pair
typedef std::pair<int, int> pii;
typedef std::vector<pii> vpii;
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
const int N = 45, M = 3e5 + 5, EI = 1e9 + 7;
int n, p[N], ToT, son[M][N], Root, f[M], C[N][N], g[N], h[N][N], fac[N];
vpii nums[M];
bool vis[N];
int dfs(int cur, vpii now)
{
int u; nums[u = ++ToT] = now; vpii nxt = now;
int lst = now.empty() ? 0 : now[now.size() - 1].first;
for (int i = lst; i <= n - cur; i++) if (i)
{
if (i == lst) nxt[nxt.size() - 1].second++;
else nxt.push_back(mp(i, 1));
son[u][i] = dfs(cur + i, nxt);
nxt = now;
}
return u;
}
void dfs_trans(int u, int dep, int v, int in, int out,
int now, vpii &trans)
{
if (dep == nums[u].size())
{
if (u == v) return;
trans.push_back(mp(1ll * now * h[out][in] % EI, v));
return;
}
int x = nums[u][dep].second, y = nums[u][dep].first;
for (int i = 0; i <= x; i++)
dfs_trans(u, dep + 1, v, in, out, 1ll * now * C[x][i] % EI, trans),
v = son[v][y], in += y, out -= y, now = EI - now;
}
int dp(int u)
{
if (f[u] != -1) return f[u];
if (u == Root) return f[u] = 1;
vpii trans; int sze = 0, cnt = 0; f[u] = 0;
for (int i = 0; i < nums[u].size(); i++)
sze += nums[u][i].first * nums[u][i].second,
cnt += nums[u][i].second;
dfs_trans(u, 0, Root, 0, sze, cnt & 1 ? 1 : EI - 1, trans);
for (int i = 0; i < trans.size(); i++)
f[u] = (1ll * trans[i].first * dp(trans[i].second) + f[u]) % EI;
return f[u];
}
int main()
{
read(n); Root = dfs(0, vpii(0));
for (int i = 1; i <= n; i++) read(p[i]);
std::vector<int> cir;
for (int i = 1; i <= n; i++) if (!vis[i])
{
int c = 0;
for (int j = i; !vis[j]; j = p[j]) c++, vis[j] = 1;
cir.push_back(c);
}
int u = 1;
std::sort(cir.begin(), cir.end());
for (int i = 0; i < cir.size(); i++) u = son[u][cir[i]];
for (int i = 0; i <= n; i++) C[i][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % EI;
memset(f, -1, sizeof(f)); fac[0] = 1;
for (int i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % EI;
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= i; j++)
g[i] = (1ll * fac[j] * fac[n - j - 1] % EI * C[i][j] + g[i]) % EI;
h[0][i] = 1;
for (int j = 1; j <= n; j++)
h[j][i] = 1ll * h[j - 1][i] * g[i] % EI;
}
return std::cout << dp(u) << std::endl, 0;
}