洛谷 P4233 射命丸文的笔记 题解

题目传送门

题目大意: n n n 个点的强连通竞赛图中的哈密顿回路的期望个数。

题解

答案需要求期望个数,那么只需要求出总的哈密顿回路个数除以强联通竞赛图个数即可。

s [ i ] s[i] s[i] 表示 i i i 个点的哈密顿回路总数,发现其实 s [ i ] s[i] s[i] 是很好算的。

s[i]的求解   因为这 i i i 个点需要强联通,也就是它们要形成至少一个环,那么为了形成这个环,我们需要用掉 i i i 条边,但是这 i i i 个点的顺序是不确定的,所以它们的排列数量就相当于它们的全排列数量,即 i ! i! i!,但是因为它们是个环,也就是说对于 a → b → c → a a\to b \to c \to a abca b → c → a → b b\to c \to a \to b bcab 来说,它们是一样的,所以方案数还要去重,也就是除以 i i i,即 i ! i \frac {i!}i ii!

还没完,一张竞赛图中有 i ( i − 1 ) 2 \frac {i(i-1)} 2 2i(i1) 条边,现在我们只用了 i i i 条,剩下还有 i ( i − 1 ) 2 − i \frac {i(i-1)} 2 -i 2i(i1)i 条边我们还没用,它们的方向可以随意,所以方案数是 2 i ( i − 1 ) 2 − i 2^{\frac {i(i-1)} 2 -i} 22i(i1)i

所以,总的方案数是: s [ i ] = i ! i × 2 i ( i − 1 ) 2 − i s[i]=\frac {i!} i \times 2^{\frac {i(i-1)}{2} -i} s[i]=ii!×22i(i1)i


剩下的问题是,如何求强联通竞赛图个数。

我们设 f [ i ] f[i] f[i] i i i 个点的强联通竞赛图个数, g [ i ] g[i] g[i] i i i 个点的竞赛图个数,显然 g [ i ] = 2 i ( i − 1 ) 2 g[i]=2^{\frac {i(i-1)} 2} g[i]=22i(i1)

我们需要求 f [ i ] f[i] f[i],但是发现难以用 g [ i ] g[i] g[i] 来表示 f [ i ] f[i] f[i],此时就要想到用 f [ i ] f[i] f[i] 来表示 g [ i ] g[i] g[i],这是多项式求逆的一大套路。

这就很简单了,枚举一个 j j j 表示这 i i i 个点中有 j j j 个点强联通,那么有:
g [ i ] = ∑ j = 1 i C i j f [ j ] g [ i − j ] g [ i ] = ∑ j = 1 i i ! j ! ( i − j ) ! f [ j ] g [ i − j ] g [ i ] = i ! × ∑ j = 1 i f [ j ] j ! g [ i − j ] ( i − j ) ! g [ i ] i ! = ∑ j = 1 i f [ j ] j ! g [ i − j ] ( i − j ) ! \begin{aligned} g[i]&=\sum_{j=1}^i C_i^jf[j]g[i-j]\\ g[i]&=\sum_{j=1}^i \frac {i!} {j!(i-j)!} f[j]g[i-j]\\ g[i]&=i! \times \sum_{j=1}^i \frac {f[j]} {j!} \frac {g[i-j]} {(i-j)!}\\ \frac {g[i]} {i!}&=\sum_{j=1}^i \frac {f[j]} {j!} \frac {g[i-j]} {(i-j)!} \end{aligned} g[i]g[i]g[i]i!g[i]=j=1iCijf[j]g[ij]=j=1ij!(ij)!i!f[j]g[ij]=i!×j=1ij!f[j](ij)!g[ij]=j=1ij!f[j](ij)!g[ij]

解法1——分治FFT

到这里大家应该也都看出来这是个分治 F F T FFT FFT 的形式了,只需要把里面右边柿子里拆一个 f [ i ] f[i] f[i] 出来,然后移一下项就可以了。

这个做法不够优秀,虽然也能 A C AC AC,但这里不多讨论。

解法2——生成函数+多项式求逆

发现这是一个指数生成函数的形式,于是设 G ( x ) = ∑ i = 0 g [ i ] i ! , F ( x ) = ∑ i = 0 f [ i ] i ! G(x)=\sum\limits_{i=0} \frac {g[i]} {i!},F(x)=\sum\limits_{i=0} \frac {f[i]} {i!} G(x)=i=0i!g[i],F(x)=i=0i!f[i],带入得:
G ( x ) = F ( x ) G ( x ) + g [ 0 ] G ( x ) = F ( x ) G ( x ) + 1 − 1 = F ( x ) G ( x ) − G ( x ) − 1 = G ( x ) ( F ( x ) − 1 ) F ( x ) = 1 − 1 G ( x ) \begin{aligned} G(x)&=F(x)G(x)+g[0]\\ G(x)&=F(x)G(x)+1\\ -1&=F(x)G(x)-G(x)\\ -1&=G(x)(F(x)-1)\\ F(x)&=1-\frac 1 {G(x)}\\ \end{aligned} G(x)G(x)11F(x)=F(x)G(x)+g[0]=F(x)G(x)+1=F(x)G(x)G(x)=G(x)(F(x)1)=1G(x)1

多项式求逆搞一搞即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define maxn 300010
#define ll long long
#define mod 998244353

int n;
ll ksm(ll x,ll y)
{
	ll re=1,tot=x;
	while(y)
	{
		if(y&1)re=re*tot%mod;
		tot=tot*tot%mod;
		y>>=1;
	}
	return re;
}
#define inv(x) ksm(x,mod-2)
ll fac[maxn],invfac[maxn];
ll G[maxn],invG[maxn],F[maxn];
void work()
{
	fac[0]=invfac[0]=1;
	for(int i=1;i<=n;i++)
	fac[i]=fac[i-1]*i%mod,invfac[i]=inv(fac[i]);
	G[0]=1;
	for(int i=1;i<=n;i++)
	G[i]=ksm(2ll,(ll)i*(i-1)/2ll)*invfac[i]%mod;
}
const int g=3,invg=inv(g);
int up,l,r[maxn];
void ntt(ll *f,int len,int type)
{
	for(int i=1;i<len;i++)
	if(i<r[i])swap(f[i],f[r[i]]);
	for(int mid=1;mid<len;mid<<=1)
	{
		ll wn=ksm((type==1?g:invg),(mod-1)/mid/2);
		for(int block=mid<<1,j=0;j<len;j+=block)
		{
			ll w=1;
			for(int i=j;i<j+mid;i++,w=w*wn%mod)
			{
				ll x=f[i],y=f[i+mid]*w%mod;
				f[i]=(x+y)%mod;f[i+mid]=(x-y+mod)%mod;
			}
		}
	}
}
ll a[maxn],b[maxn];
void work(int len)
{
	up=1;l=0;
	while(up<=len)up<<=1,l++;
	for(int i=1;i<up;i++)
	r[i]=(r[i>>1]>>1)|((i&1)<<(l-1)),a[i]=b[i]=0;
}
void solve(int len,ll *arr,ll *invarr)
{
	if(len==1){invarr[0]=inv(arr[0]);return;}
	solve(len+1>>1,arr,invarr);
	work(len+n);
	for(int i=0;i<up;i++)a[i]=arr[i];
	for(int i=0;i<(len+1>>1);i++)b[i]=invarr[i];
	ntt(a,up,1);ntt(b,up,1);
	for(int i=0;i<up;i++)
	a[i]=b[i]*((2-a[i]*b[i]%mod+mod)%mod)%mod;
	ntt(a,up,-1);
	ll invup=inv(up);
	for(int i=0;i<len;i++)
	invarr[i]=a[i]*invup%mod;
}
void workF()
{
	for(int i=0;i<=n;i++)
	F[i]=(mod-invG[i])%mod;
	F[0]=(F[0]+1)%mod;
	for(int i=0;i<=n;i++)
	F[i]=F[i]*fac[i]%mod;
}

int main()
{
	scanf("%d",&n);
	work();
	solve(n+1,G,invG);
	workF();
	if(n>=1)printf("1\n");
	if(n>=2)printf("-1\n");
	for(int i=3;i<=n;i++)
	printf("%lld\n",fac[i-1]*ksm(2,(ll)i*(i-1)/2ll-i)%mod*inv(F[i])%mod);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值