[ACNOI2022]为卷爷所吊打

题目

题目背景
一直以为我和 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)1in,1jnai} 。式中变量均为整数。

求图中有多少个简单环(不经过重复的点;至少包含两个点)。两个环不同,当且仅当构成环的点集不同。

数据范围与提示
max ⁡ { a i } ⩽ n ⩽ 5 × 1 0 3 \max\{a_i\}\leqslant n\leqslant 5\times 10^3 max{ai}n5×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(axi1,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=1n(yii+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)jg(j) 。当然,应为叠加,即该点不选。

然后加入点 i i i 。它可以连接两条链,所以 g ( j − 1 ) ← j ⋅ g ( j ) g(j{-}1)\gets j\cdot g(j) g(j1)jg(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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值