题解:AT_arc140_d [ARC140D] One to One
2024 年 10 月 31 日:修改状态、转移方程、统计答案部分的文字表述,以及转移方程的公式错误。\ 2024 年 11 月 11 日:更详细地解释式子的含义。
题面翻译
你有一个长度为
初始时有编号为
题目讲解
注意到一个美妙的性质。\
先把所有不是
因此确定完所有
发现性质之后就是神仙计数题了,设有
发现
其中
考虑如何统计答案。基环树和环的答案已经统计过了,而我们现在要统计树连通块的贡献。
它的意思是,从
略微提一下,这样的状态设计就已经能过了,不过当然可以把 DP 压缩到一维,倒序转移,这和背包是类似的。\
注意开 long long。
时间复杂度 码风可能比较抽象请见谅。
#include <bits/stdc++.h>
using namespace std;
const int N = 2015, M = N << 1;
const int mod = 998244353;
int n, a[N], dp[N][N];
int h[N], e[M], ne[M], idx = 0;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool st[N];
int sz[N];
void dfs(int u, int root) {
st[u] = 1, sz[root]++;
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (st[v]) continue;
dfs(v, root);
}
}
int m, b[N], fac[N];
int qmi(int a, int k) {
int res = 1;
while (k) {
if (k & 1) res = (res * 1ll * a) % mod;
a = (a * 1ll * a) % mod;
k >>= 1;
}
return res;
}
int main() {
scanf("%d", &n);
fac[0] = 1;
for (int i = 1; i <= n; i++) h[i] = -1, fac[i] = (fac[i - 1] * 1ll * i) % mod;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
if (a[i] != -1) add(i, a[i]), add(a[i], i);
}
for (int i = 1; i <= n; i++)
if (a[i] == -1) dfs(i, i), b[++m] = i;
dp[0][0] = 1;
for (int i = 1; i <= m; i++) {
for (int j = 0; j <= i; j++)
dp[i][j] = ((dp[i - 1][j - 1] * 1ll * sz[b[i]]) % mod + dp[i - 1][j]) % mod;
}
long long ans = 0;
for (int i = 1; i <= m; i++)
(ans += ((dp[m][i] * 1ll * fac[i - 1]) % mod * 1ll * qmi(n, m - i)) % mod) %= mod; //树之间的连边
for (int i = 1; i <= n; i++)
if (!st[i]) dfs(i, i), (ans += qmi(n, m)) %= mod; //基环树连通块
printf("%lld\n", ans);
return 0;
}