题目
题目背景
一直以为我和
Bitset
Dog
\textsf{Bitset Dog}
Bitset Dog 不一样;原来被
O
U
Y
E
\sf OUYE
OUYE 吊打的时候,所有的生物都一样。
那天下午我做了个梦,我被 O U Y E \sf OUYE OUYE 润得体无完肤,走出机房的时候,我以为我会醒来,谁知道,原来有些梦是永远不会醒的。
题目描述
给出二分图
G
=
(
V
,
E
)
G=(V,E)
G=(V,E),其中
V
=
[
1
,
2
n
]
V=[1,2n]
V=[1,2n],边集
E
=
{
(
i
,
j
)
∣
1
⩽
i
⩽
n
,
1
⩽
j
−
n
⩽
a
i
}
E=\{(i,j)\;|\;1\leqslant i\leqslant n,\;1\leqslant j-n\leqslant a_i\}
E={(i,j)∣1⩽i⩽n,1⩽j−n⩽ai} 。式中变量均为整数。
求图中有多少个简单环(不经过重复的点;至少包含两个点)。两个环不同,当且仅当构成环的点集不同。
数据范围与提示
max
{
a
i
}
⩽
n
⩽
5
×
1
0
3
\max\{a_i\}\leqslant n\leqslant 5\times 10^3
max{ai}⩽n⩽5×103 。
思路
假设已知经过
[
1
,
n
]
[1,n]
[1,n] 内的点,依次是
x
1
,
x
2
,
…
,
x
k
x_1,x_2,\dots,x_k
x1,x2,…,xk,则记
y
i
=
min
(
a
x
i
−
1
,
a
x
i
)
(
x
0
=
x
k
)
y_i=\min(a_{x_{i-1}},a_{x_i})\;(x_0=x_k)
yi=min(axi−1,axi)(x0=xk),将
y
i
y_i
yi 从小到大排序为
{
y
′
}
\{y'\}
{y′},则选取
(
n
,
+
∞
)
(n,+\infty)
(n,+∞) 点的方案数是
∏
i
=
1
n
(
y
i
′
−
i
+
1
)
\prod_{i=1}^{n}(y'_i-i+1)
i=1∏n(yi′−i+1)
于是从小到大将 a i a_i ai 排序,然后连续段 d p \tt dp dp 即可做到 O ( n 3 ) \mathcal O(n^3) O(n3) 。
但这个做法很难优化。要充分利用数据范围的约束。注意到 a i a_i ai 的值域很小,我们可以设计一个基于值域的做法。
吐槽:我猜其本质是将贡献均摊到了 a i a_i ai 上,
但是我没有证据。我全然不知怎么做到这一点。
既然不能只选 [ 1 , n ] [1,n] [1,n] 直接算数量,那就把所有点纳入 d p \tt dp dp 考虑范畴。记 f ( i , j ) f(i,j) f(i,j) 为,考虑 [ 1 , i ] ∪ ( n , n + a i ] [1,i]\cup(n,n{+}a_i] [1,i]∪(n,n+ai] 的点,此时接出的 j j j 条链的圆排列种类数——类似连续段 d p \tt dp dp 。
转移到 f ( i + 1 , j ′ ) f(i{+}1,j') f(i+1,j′) 分两步。为了方便,下简记为 g ( j ) g(j) g(j) 。首先将 ( n + a i , n + a i + 1 ] (n{+}a_i,n{+}a_{i+1}] (n+ai,n+ai+1] 纳入考虑。相当于加入若干新点。加入时,可以在圆排列上任意插入, g ( j + 1 ) ← j ⋅ g ( j ) g(j{+}1)\gets j\cdot g(j) g(j+1)←j⋅g(j) 。当然,应为叠加,即该点不选。
然后加入点 i i i 。它可以连接两条链,所以 g ( j − 1 ) ← j ⋅ g ( j ) g(j{-}1)\gets j\cdot g(j) g(j−1)←j⋅g(j) 。再没别的操作。
答案就是 f ( n , 0 ) f(n,0) f(n,0) 。初值可以设为 f ( 0 , 0 ) = 1 f(0,0)=1 f(0,0)=1,这会导致二元环的出现,最后减去。或者,初值只考虑两段的情况,避免单元素自成单段。
f f f 第二维的范围是 max ( n , a ) \max(n,a) max(n,a),所以总复杂度 O [ max ( n , a ) 2 ] \mathcal O[\max(n,a)^2] O[max(n,a)2] 。
代码
代码实现极其简单,故 O U Y E \sf OUYE OUYE 等均以其为签到题。
#include <cstdio> // megalomaniac JZM yydJUNK!!!
#include <iostream> // Almighty XJX yyds!!!
#include <algorithm> // decent XYX yydLONELY!!!
#include <cstring> // Casual-Cut DDG yydOLDGOD!!!
#include <cctype> // oracle: ZXY yydBUS!!!
typedef long long llong;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar()) if(c == '-') f = -f;
for(; isdigit(c); c=getchar()) a = a*10+(c^48);
return a*f;
}
const int MAXN = 5005, MOD = 998244353;
inline int modAdd(int x, const int &y){
return (x += y) >= MOD ? x-MOD : x;
}
const int inv2 = (MOD+1)>>1;
int cnt[MAXN], dp[MAXN];
int main(){
int n = readint();
rep(i,1,n) ++ cnt[readint()];
for(int a=1; a<=n; ++a){
drep(i,n-1,1) dp[i+1] = int((llong(i)*dp[i]+dp[i+1])%MOD);
dp[2] = modAdd(dp[2],a-1); // avoid reusing edge
while(cnt[a] --) // one more plug
rep(i,1,n) dp[i-1] = int((llong(i)*dp[i]+dp[i-1])%MOD);
}
printf("%lld\n",llong(dp[0])*inv2%MOD);
return 0;
}