【CometOJ4304】小 C 的奇妙序列(组合意义)(正整数拆分)(一言难尽)

传送门


题解:

花了整整三个小时。。。

一开始往期望的线性性考虑,花了半天时间拆式子发现是个套娃整个人都不好了。

然后突然发现了这个东西的组合意义。

考虑我们每个点 i i i [ 0 , i ) [0,i) [0,i) 随机连出 k k k 条边,允许重边且每条边互不相同,从点 0 0 0 跑出 k k k 个不同的小球,全部走到 n n n 的方案数,注意每条边都允许多个球通过。

这道题问的期望就是所有连边方案下上面的方案数之和除以方案总数。

于是一个比较显然的思路是记录哪些球停在哪个点来DP。

然后你会发现我们在转移系数上其实只关心数量而不关心实际是哪些,于是可以爆搜正整数拆分然后预处理转移。

但是一对一转移状态还是太多了,考虑下一个优化。

f [ i ] [ s t ] f[i][st] f[i][st] 表示所有球目前在前 i i i 个点上,且除了在 i i i 点以上的所有球按照下一条进行转移的边划分后形成正整数拆分为 s t st st 的方案数。

g [ i ] [ s t ] g[i][st] g[i][st] 表示所有球在前 i i i 个点上且按照下一条进行转移的边进行划分后正整数拆分为 s t st st 的方案数。

注意这里强行要求在拆分中没有划分到一起的点不得使用同一条边进行转移,且这里进行边划分之后不要求立即进行转移

这样转移很好写而且转移数量也比一对一转移少了很多,可以AC,转移方式就是考虑还原标号的方案数和分配方案数即可,具体实现可以看代码。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

using std::cerr;
using std::cout;
using pii=std::pair<int,int>;
#define fi first
#define se second 

cs int mod=998244353;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a-b<0?a-b+mod:a-b;}
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline void Inc(int &a,int b){a+=b-mod;a+=a>>31&mod;}
inline void Dec(int &a,int b){a-=b;a+=a>>31&mod;}
inline void Mul(int &a,int b){a=mul(a,b);}
inline int po(int a,int b){int r=1;for(;b;b>>=1,Mul(a,a))if(b&1)Mul(r,a);return r;}
inline void ex_gcd(int a,int b,int &x,int &y){
	if(!b){x=1,y=0;return;}ex_gcd(b,a%b,y,x);y-=a/b*x;
}inline int inv(int a){int x,y;ex_gcd(mod,a,y,x);return x+(x>>31&mod);}

int fac[15];
inline int A(int n,int m){return fac[n]/fac[n-m];}
inline int C(int n,int m){return fac[n]/fac[m]/fac[n-m];}

cs int N=1e5+7;
cs int F=153,G=57;


int n,K,fct,gct,f[F],g[G];
struct atom{int id,coe,k;};
std::vector<atom> tf[F],tg[G];
std::map<std::vector<int>,int> fid,gid;
std::vector<std::vector<int> > p[15];

void dfs(int n,int rest,int las){
	static std::vector<int> vec;
	if(!rest)return p[n].push_back(vec);
	for(int i=las;i<=rest;++i){
		vec.push_back(i);
		dfs(n,rest-i,i);
		vec.pop_back();
	}
}

void trans(int x,cs std::vector<int> &u,cs std::vector<int> &v){
	int ct=1,coe=1,y=K-x;
	static int Cu[11],Cv[11];
	memset(Cu,0,sizeof Cu);
	memset(Cv,0,sizeof Cv);
	std::vector<int> p;
	p.resize(u.size()+v.size());
	std::merge(u.begin(),u.end(),
		v.begin(),v.end(),p.begin());
	for(int t:v){
		coe*=C(y,t);
		y-=t;++Cv[t];
	}for(int t:u)++Cu[t];
	for(int re i=1;i<=K;++i){
		ct*=C(Cu[i]+Cv[i],Cu[i]);
		coe/=fac[Cv[i]];
	}int fi=fid[u],gi=gid[p];
	tf[fi].push_back({gi,coe,0});
	tg[gi].push_back({fi,mul(A(K,v.size()),ct),(int)v.size()});
}

void init(){
	for(int re i=fac[0]=1;i<=10;++i)
		fac[i]=fac[i-1]*i;
	for(int re i=0;i<=K;++i){
		dfs(i,i,1);
		for(cs auto &v:p[i])
			fid[v]=++fct;
	}for(cs auto &v:p[K])
		gid[v]=++gct;
	for(int re i=0;i<=K;++i)
		for(cs auto &u:p[i])
			for(cs auto &v:p[K-i])
				trans(i,u,v);
}

int pw[15];
void Main(){
	scanf("%d%d",&n,&K);
	init(),f[1]=pw[0]=1;
	for(int re i=1;i<=n;++i){
		memset(g+1,0,gct<<2);
		for(int re j=1;j<=fct;++j)
			if(f[j])for(auto &t:tf[j])
				Inc(g[t.id],mul(f[j],t.coe));
		for(int re j=1;j<=K;++j)
			pw[j]=mul(pw[j-1],i);
		memset(f+1,0,fct<<2);
		for(int re j=1;j<=gct;++j)
			if(g[j])for(auto &t:tg[j])
				Inc(f[t.id],mul(g[j],mul(pw[K-t.k],t.coe)));
	}int tot=1;
	for(int re i=1;i<=n;++i)Mul(tot,i);
	cout<<mul(f[1],inv(po(tot,K)))<<"\n";
}

inline void file(){
#ifdef zxyoi
	freopen("nightmare.in","r",stdin);
#else
#ifndef ONLINE_JUDGE
	freopen("nightmare.in","r",stdin);
	freopen("nightmare.out","w",stdout);
#endif
#endif
}signed main(){file();Main();return 0;} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值