BZOJ 5093: 图的价值 第二类斯特林数 NTT

5093: 图的价值

Time Limit: 30 Sec  Memory Limit: 256 MB
Submit: 176  Solved: 89
[Submit][Status][Discuss]

Description

“简单无向图”是指无重边、无自环的无向图(不一定连通)。
一个带标号的图的价值定义为每个点度数的k次方的和。
给定n和k,请计算所有n个点的带标号的简单无向图的价值之和。
因为答案很大,请对998244353取模输出。

Input

第一行包含两个正整数n,k(1<=n<=10^9,1<=k<=200000)。

Output

 输出一行一个整数,即答案对998244353取模的结果。

Sample Input

6 5

Sample Output

67584000

这是一篇超详尽的题解:

 

题意求n个点组成的所有简单无向图的所有点的度数k次方之和

 

BJ 连最开始的式子都没写出来 太弱弱弱弱了 泪

考虑 因为要计算所有情况 则每个点的贡献是相同的

// 第一步没想到 太太太弱了

那么接下来只要枚举度数 并计算有多少图满足条件即可

这样 我们就可以考虑开始对它进行化简了

// 为了方便书写 提出来的常数会忽略

一般对于一个k次方求和的形式我们都可以利用第二类斯特林数的性质化简

所以这个式子就化为

这时候式子较为复杂 化简方式也不止一种

比如说 是否改变枚举顺序 是否用通项拆开斯特林数 是否把组合数拆成阶乘

仅考虑这三点 我们就会有8种化简方法

但是一定是可以找到最优的化简方案的

首先 这个题的n看起来很不好处理范围是1e9

这一定是不允许枚举的 卷积也搞不了他

这时 唯一的解决方法就是利用斯特林数的枚举上限是min(n,K)进行限制

那么我们改变枚举顺序

之后 考虑斯特林数的处理

如果拆开式子就又会多一重枚举 就不好压掉了

而且K是定值一行斯特林数可以卷积卷出来

那么先不拆

I! 是常数 姑且先提出来

现在就要思考如何压掉deg的枚举了

这个时候就出现了一个高级操作

我们考虑的意义!!!

把式子倒过来 n-1个里选deg个 deg个里再选i个

等价于 n-1个里选i个 剩下的随便选!!

这样一来 便化腐朽为神奇

它摇身一变

这样就可以直接枚举啦

Yeah


#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<string>
#include<bitset>
#include<queue>
#include<map>
#include<set>
using namespace std;

typedef long long ll;

inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void print(int x)
{if(x<0)putchar('-'),x=-x;if(x>=10)print(x/10);putchar(x%10+'0');}

const int N=525000,mod=998244353;

inline int qpow(int x,int y)
{
	int res(1);
	while(y)
	{
		if(y&1) res=1ll*res*x%mod;
		x=1ll*x*x%mod;
		y>>=1;
	}
	return res;
}

int r[N];

void ntt(int *x,int lim,int opt)
{
	register int i,j,k,m,gn,g,tmp;
	for(i=0;i<lim;++i)
	{
		r[i]=(i&1)*(lim>>1)+(r[i>>1]>>1);
		if(r[i]<i) swap(x[i],x[r[i]]);
	}
	for(m=2;m<=lim;m<<=1)
	{
		k=m>>1;
		gn=qpow(3,(mod-1)/m);
		for(i=0;i<lim;i+=m)
		{
			g=1;
			for(j=0;j<k;++j,g=1ll*g*gn%mod)
			{
				tmp=1ll*x[i+j+k]*g%mod;
				x[i+j+k]=(x[i+j]-tmp+mod)%mod;
				x[i+j]=(x[i+j]+tmp)%mod;
			}
		}
	}
	if(opt==-1)
	{
		reverse(x+1,x+lim);
		int inv(qpow(lim,mod-2));
		for(i=0;i<lim;++i) x[i]=1ll*x[i]*inv%mod;
	}
}

int A[N],B[N],S[N];

int fac[N],inv[N];

int main()
{
	int n=read(),K=read();
	register int i,j,lim(1);
	fac[0]=1;for(i=1;i<=K;++i) fac[i]=1ll*fac[i-1]*i%mod;
	inv[1]=1;for(i=2;i<=K;++i) inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
	inv[0]=1;for(i=1;i<=K;++i) inv[i]=1ll*inv[i-1]*inv[i]%mod;
	
	for(i=0;i<=K;++i) A[i]=i&1 ? mod-inv[i] : inv[i];
	for(i=0;i<=K;++i) B[i]=1ll*qpow(i,K)*inv[i]%mod;
	
	while(lim<=(K<<1)) lim<<=1;
	ntt(A,lim,1);ntt(B,lim,1);
	for(i=0;i<lim;++i) S[i]=1ll*A[i]*B[i]%mod;
	ntt(S,lim,-1);
	
	int ans(0);
	for(i=0,j=1;i<=min(n-1,K);++i,j=1ll*j*(n-i)%mod)
		(ans+=1ll*S[i]*j%mod*qpow(2,n-i-1)%mod)%=mod;
	ans=1ll*ans*n%mod;
	ans=1ll*ans*qpow(2,(1ll*(n-1)*(n-2)>>1)%(mod-1))%mod;
	cout<<ans<<endl;
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值