HUSTPC2022

H. Permutation Counting

思路:
n ( 1 ≤ n ≤ 2 ⋅ 1 0 6 ) n(1≤n≤2⋅10^6) n(1n2106)个数字的全排列 a a a,已知这 n n n个数字间有 m m m个约束 x i , y i x_i,y_i xi,yi,已知 a x i < a y i a_{xi}<a_{yi} axi<ayi,且 x i x_i xi互不相同, y i y_i yi可能相同
问有多少种满足所有约束的可能?对答案模 998244353 998244353 998244353
思路:
根据约束建立有向图,若图中存在环,则认为无解。反之,则一定至少存在一种合理解。
其次,由于题目中 x i x_i xi互不相同,我们可以得知,若将 y i → x i y_i \rightarrow x_i yixi连边,则一定会形成森林
为了将森林变为一整颗树,加入额外点 n + 1 n+1 n+1,与所有树的树根连接,于是我们将森林变为了一整颗树
树形 d p dp dp s z i sz_i szi表示以 i i i为树根的子树节点大小, f i f_i fi表示 s z i sz_i szi个数字在以 i i i为根的子树中,有多少种排列方法。那么
考虑将 10 10 10个不同的石头分成 5 , 3 , 2 5,3,2 5,3,2三堆
C 10 5 ⋅ C 5 3 ⋅ C 2 2 C_{10}^{5}⋅C_5^3⋅C_2^2 C105C53C22种可能
那么本题将一颗大小的 10 10 10个节点的树分为 4 , 3 , 2 4,3,2 4,3,2的子树
C 9 4 ⋅ C 5 3 ⋅ C 2 2 C_{9}^{4}⋅C_5^3⋅C_2^2 C94C53C22种可能,其中最大的数字一定是根节点。
所以只需要满足根节点最大,剩下的节点任意分即可

/*
 * @Author: zhangpangpang
 * @Date: 2022-06-09 09:29:49
 * @Last Modified by: zhangpangpang
 * @Last Modified time: 2022-06-09 09:29:49
 */
#include<bits/stdc++.h>
#define fi first
#define gcd __gcd
#define se second
#define pb push_back
#define lowbit(x) x&-x
#define inf 0x3f3f3f3f
#define mem(x,y) memset(x,y,sizeof(x))
#define lrb666 ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
typedef pair<int,int> PII;
const int maxn=2e6+7;
int n,m,fa[maxn],d[maxn];
ll mod=998244353,sz[maxn],f[maxn],fac[maxn],inv[maxn],finv[maxn];
ll ksm(ll b,ll p){ll r=1;while(p>0){if(p&1){r=(r%mod*(b%mod))%mod;}p>>=1;b=(b%mod*(b%mod))%mod;}return r;}
void binom_init(int x) {
    fac[0] = fac[1] = 1;
    inv[1] = 1;
    finv[0] = finv[1] = 1;
    for(int i=2; i<x; i++){
        fac[i] = fac[i-1]*i%mod;
        inv[i] = mod-mod/i*inv[mod%i]%mod;
        finv[i] = finv[i-1]*inv[i]%mod;
    }
}
ll binom(ll n, ll r){
    if(n<r || n<0 || r<0) return 0;
    return fac[n]*finv[r]%mod*finv[n-r]%mod;
}
int find(int x)
{
	if(x==fa[x])
	return fa[x];
	else
	return fa[x]=find(fa[x]);
}
void lianjie(int x,int y)
{
	int px=find(x),py=find(y);
	fa[px]=py;
}
vector<int>v[maxn];
void dfs(int u,int fa)
{
	sz[u]=1;f[u]=1;
	ll use=0;
	for(auto nx:v[u])
	{
		if(u==fa) continue;
		dfs(nx,u);
		sz[u]+=sz[nx];
	}
	use++;
	for(auto nx:v[u])
	{
		if(u==fa) continue;
		f[u]=(f[u]*f[nx])%mod;
		f[u]=(f[u]*binom(sz[u]-use,sz[nx]))%mod;
		use+=sz[nx];
	}
}
int main()
{
	binom_init(maxn-5);
	scanf("%d%d",&n,&m);int ok=1;
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		int a,b;scanf("%d%d",&a,&b);
		d[a]++;
		v[b].pb(a);
		if(find(a)==find(b)) ok=0;
		else lianjie(a,b);
	}
	for(int i=1;i<=n;i++)
	{
		if(d[i]==0) v[n+1].pb(i);
	}
	if(!ok)
	{
		puts("0");return 0;
	}
	dfs(n+1,-1);
	printf("%lld\n",f[n+1]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值