【2020杭电多校round5】HDU6818 Array Repairing

题目大意

题目链接

对于一个给定的正整数(n),随机生成一个正整数序列(a[1dots n]),满足(forall iin[1,n]:a_iin[1,n])。注意,(a)不一定是排列。

你需要通过若干次操作,使得(forall iin[1,n],a_i=i)。你可以进行的操作有两种:

  1. 选择一对位置(i,jin[1,n])(i eq j),交换(a_i,a_j)。一次操作花费为(1)
  2. 选择一个位置(iin[1,n])和一个数值(vin[1,n]),令(a_i:=v)。一次操作花费为(k)

例如,如果你要进行(5)次1操作和(4)次2操作,则总花费为(4 imes k+5)

( ext{cost}_k(a))表示在参数(k)的条件下,将序列(a)变成(1,2,dots ,n)的最小花费。对(k=0,1,2),你需要分别求出( ext{cost}_k(a))的期望值(E( ext{cost}_k(a)))。输出(mod 998244353)意义下的值。

给定(N)。对所有(nin[1,N])分别求答案。也就是说,你一共需要输出(3N)个数。

数据范围:(1leq Nleq 5 imes10^5)

本题题解

我们称把(a)序列变成(1,2,dots,n)的过程为“还原”这个序列。一个位置(i)“已经还原”,就是指(a_i=i)

首先,(k=0)时,代价一定为(0)

subtask (k=1)

考虑(k=1)的情况。我们从每个(i)(a_i)连一条有向边,则能得到一个基环(内向)树森林。发现,对每个环,设其长度为( ext{len}),我们使用( ext{len}-1)次1操作可以将其还原(环上每个点(a_i=i))。剩下的部分只能用2操作完成。容易证明这样花费是最小的。

对于一个长度为(n)的序列(a),设它对应的基环树森林里,环的数量为(c)。则将(a)序列还原的最小花费就是(n-c)。注意,自环也算,也就是说“环的数量”和“连通块的数量”是等价的。

要求期望,我们乘以序列的总数(n^n)以后,就转化为求所有序列的最小花费之和。又因为最小花费为(n- ext{环的数量})(sum(n- ext{环的数量})=sum n-sum ext{环的数量}=n^ncdot n-sum ext{环的数量})。问题转化为求(sum ext{环的数量}),也就是所有基环树森林,环的数量之和,记为(h(n))

可以写出式子:(h(n)=sum_{i=1}^{n}(i-1)!{nchoose i}n^{n-i})。含义:((i-1)!)表示长度为(i)的环有多少种,({nchoose i})表示选出一些位置作为这个环,(n^{n-i})表示其他位置随意。不过要对每个(n)求一次,时间复杂度是(O(N^2))的,无法承受。比赛时,你可以暴力求出前几个(h)值,然后去oeis上找到这个序列:A190314。不过还有更好的方法。

考虑用生成函数做。设(f(n))表示(n)个点的基环树数量(这里指的是一棵(n)个节点的、完整的基环树有多少种,而不是基环树森林)。这个(f)我们暂时还不会求。

再设一个(g(n))表示(n)个点的基环树森林数量。这是很好求的,就等于(n^n)。设(F(x),G(x))分别是(f,g)指数型生成函数(即(F(x)=sum_{i=0}^{infty}f(x)frac{x^i}{i!})(G)同理)。则根据指数型生成函数的理论,可知:(G(x)=exp(F(x)))。所以(F(x)=ln(G(x)))。这样,通过做多项式(ln),我们就求出了(f)序列。

知道了(f,g)以后。设(h)的指数型生成函数为(H(x)),则(H(x)=F(x)G(x))。这相等于,考虑每个基环树,对总数的贡献。单独拿出一棵(i)个节点的基环树,首先这样的基环树有(f(i))个,剩下的部分可以任意形成一个基环树森林,方案数就是(g(n-i))(注意,特别地,这里我们认为(g(0)=1)),而从(n)个点里选出这(i)个点的方案数是({nchoose i}),所以(h(n)=sum_{i=0}^{n}{nchoose i}f(i)g(n-i))。这正是指数型生成函数卷积的定义。

总结来说,我们先搞一个序列(g(n)=n^n)。然后(G(x)cdot ln G(x)),就是我们要求的环的总数(h)了。答案就是(displaystylefrac{n^ncdot n-h(n)}{n^n})。(除以(n^n)是因为要求期望)。

时间复杂度(O(Nlog N))

subtask (k=2)

(k=2)时,环上的点,我们还是用1操作来完成。考虑剩下的点。相当于要把每个点,变成它的儿子上面现在的值(显然每个儿子上的值是一样的)。所以对每个点,我们随便挑个儿子和它做交换(操作1)。直到叶子节点,没有儿子了,我们再用操作2。总的代价是(n- ext{环的数量}+ ext{叶子数量})

环的数量我们已经会求了。考虑叶子数量。

考虑每个点作为叶子时,对总数的贡献。它的贡献数,就等于钦定它是叶子后,其他点组成基环树森林的总方案数。也就是((n-1)^{n-1}),此外,这个点自己还要随便选一个父亲,所以要乘以(n-1),也就是((n-1)^{n})。这个思想挺妙的,把求所有基环树的叶子树之和,转化为了求每个叶子出现在多少基环树里。

所以所有叶子的总贡献就是(ncdot (n-1)^n)。答案就是:(displaystylefrac{n^ncdot n-h(n)+ncdot (n-1)^n}{n^n})

时间复杂度(O(Nlog N))

参考代码

( ext{NTT})和多项式(ln)的部分省略了,自己贴板子吧。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAXN=5e5;
const int MOD=998244353;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}

int fac[MAXN+5],ifac[MAXN+5];
inline int comb(int n,int k){
	if(n<k)return 0;
	return (ll)fac[n]*ifac[k]%MOD*ifac[n-k]%MOD;
}
void facinit(int lim=MAXN){
	fac[0]=1;
	for(int i=1;i<=lim;++i)fac[i]=(ll)fac[i-1]*i%MOD;
	ifac[lim]=pow_mod(fac[lim],MOD-2);
	for(int i=lim-1;i>=0;--i)ifac[i]=(ll)ifac[i+1]*(i+1)%MOD;
}

int f[MAXN+5],g[MAXN+5],h[MAXN+5];

int main() {
	facinit();
	
	for(int i=1;i<=MAXN;++i){
		g[i]=(ll)pow_mod(i,i)*ifac[i]%MOD;
	}
	g[0]=1;
	Ln::main(g,f,MAXN+1);
	Mul::main(g,f,h,MAXN+1);
	for(int i=0;i<=MAXN;++i){
		h[i]=(ll)h[i]*fac[i]%MOD;
	}
	int n;cin>>n;
	for(int i=1;i<=n;++i){
		int ans=h[i]%MOD;
		int tot=pow_mod(i,i);
		ans=mod2((ll)tot*i%MOD - ans);
		ans=(ll)ans*pow_mod(tot,MOD-2)%MOD;
		cout<<0<<" "<<ans<<" ";
		int x=pow_mod(i-1,i);
		int y=pow_mod(i,i-1);
		x=(ll)x*pow_mod(y,MOD-2)%MOD;
		add(ans,x);
		cout<<ans<<endl;
	}
	return 0;
}
原文地址:https://www.cnblogs.com/dysyn1314/p/13436563.html