CF1466H Finding satisfactory slutions 题解
\(\text{CF1466H Finding satisfactory slutions 题解}\)
这个题踏马有点屌。
首先这个 dick 的条件我们根本没法刻画。题目给了你最优分配要求你寻找满足条件的配置序列,注意到题目中有一段话:"可以证明,对于每一种偏好配置,恰好存在唯一一个最优分配。",我们先考虑如何解决每种配置找到唯一最优分配的这一过程。
我们考虑每对选择 \((i,p(i))\) 都在 \(1\sim n\) 内,换句话说将 \(i\to p(i)\) 连边那么形成了一个类似于内向基环树的东西。考虑不合法的条件是有子集满足更换后没有变得更劣但有变得更优的点,发现将环上一个点的出边移动后会使得其可以变得更优切不改变环上其它点的出边。那么我们可以递归地解决这个问题:每次删去一个环,将指向环上的点改为指向其余点中最优的点,递归。
那么让我们抽象出来一个合法的图论模型:先连所有有向边 \(i\to p(i)\),考虑在答案排列中所有比 \(p(i)\) 更靠前的点 \(j\) 连边 \(i\to j\),合法方案显然要满足所有 \(i\to j\) 都不在环里,否则所有对应的环是不合法的。
现在考虑计数。先考虑一个图对应多少个答案序列。称 \(i\to p(i)\) 为黑边,\(i\to j\) 为白边,那所有黑边是确定的。我们记 \(d_i\) 表示 \(i\) 连出去的白边的数目,那么显然对应的序列个数为:
这个东西的含义就是分成两类,内部可以随意排列。
那么我们现在来考虑统计带权图的个数。先不考虑黑边,对于白边实际上缩点后形成了一堆 DAG,我们设 \(dp_S\) 表示点集 \(S\) 生成子图对应 DAG 的权值和,那么有转移:
这个容斥系数是因为 \(T\) 相当于是最后一层的集合,我们钦定它们的入度为 \(0\) 但显然要通过容斥化成入度恰好为 \(0\) 的情形。\(f(A,B)\) 指的是 \(B\) 中元素向 \(A\) 中连边的权值,然后你会发现每个点出边都是独立的,因此直接 \(|B|\) 次方即可,于是计算一个节点向一个大小为 \(s\) 的集合连边的权值 \(f(s)\)。枚举连的边数,有:
实际上这个东西就是 \(\dfrac{n!}{n-s}\),不过暴力预处理其实也能通过。
然后你会发现这个东西复杂度有一个枚举子集的类似于 \(3^n\) 的东西。但是这个东西其实只和集合大小相关,于是你压一下状态会发现这个东西在 \(n=40\) 时只有 \(1440\),然后用一个类似于 Hash 一样的东西压状态就能做了。
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2505, mod = 1e9 + 7;
int n, a[N];
bool vis[N];
int siz, num[N];
void dfs(int x) {
++siz;
vis[x] = 1;
if (!vis[a[x]]) dfs(a[x]);
}
int fac[N], inv[N];
int C(int n, int m) {
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int qpow(int x, int y) {
int ans = 1;
while (y) {
if (y & 1) ans = ans * x % mod;
x = x * x % mod;
y >>= 1;
}
return ans;
}
int pre[N], lst[N], dp[N], mx;
void enc(int *a, int x) {
for (int i = 1; i <= mx; i++) {
a[i] = x % (num[i] + 1);
x /= num[i] + 1;
}
}
int F(int a, int b) {
return qpow(fac[n] * qpow(n - a, mod - 2) % mod, b);
}
signed main() {
fac[0] = 1;
for (int i = 1; i < N; i++) fac[i] = fac[i - 1] * i % mod;
inv[N - 1] = qpow(fac[N - 1], mod - 2);
for (int i = N - 2; ~i; --i) inv[i] = inv[i + 1] * (i + 1) % mod;
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++)
if (!vis[i]) {
dfs(i);
++num[siz];
mx = max(mx, siz);
siz = 0;
}
int sta = 0;
for (int i = mx; i; --i) sta = sta * (num[i] + 1) + num[i];
dp[0] = 1;
for (int i = 1; i <= sta; i++) {
enc(lst, i);
for (int j = 0; j < i; j++) {
enc(pre, j);
int presm = 0, lstsm = 0, prect = 0, lstct = 0, tmp = 1;
for (int k = 1; k <= mx; k++)
if (lst[k] < pre[k]) goto W;
for (int k = 1; k <= mx; k++) {
lstsm += lst[k], lstct += lst[k] * k;
presm += pre[k], prect += pre[k] * k;
tmp = tmp * C(lst[k], pre[k]) % mod;
}
tmp = tmp * dp[j] % mod * F(prect, lstct - prect) % mod;
if ((lstsm - presm) & 1) dp[i] = (dp[i] + tmp) % mod;
else dp[i] = (dp[i] - tmp + mod) % mod;
W:;
}
}
cout << dp[sta] << "\n";
return 0;
}

浙公网安备 33010602011771号