NOI Online #2 普及组 第三题:建设城市

25 篇文章 0 订阅
11 篇文章 0 订阅

前言

这道题是我今天刷的,意为完成Noi Online #2的普及组所有题的复盘

这道题是一道 提高+/省选- \colorbox{blue}{\text{提高+/省选-}} 提高+/省选-的题目,也不是算太难

题目

传送门

解析

前置知识:快速幂,组合数学,逆元


我们首先设一个函数 f ( i , j ) f(i,j) f(i,j)表示值域为 1 + x ∼ j + x ∣ x ∈ N 1+x \sim j+x|x\in N 1+xj+xxN(可以证明不管 x x x为多少都对答案没有多大关系),序列长度为 i i i的序列的排列方案。

然后由插板法可得

f ( i , j ) = ( i + j − 1 j − 1 ) f(i,j)=\dbinom{i+j-1}{j-1} f(i,j)=(j1i+j1)

对于答案的统计,需要分情况讨论

  • x ≤ n , y > n x\le n,y>n xn,y>n

对于这种情况我们可以做一个图示:

在这里插入图片描述在此时,这个序列便分为四个区间: 1 ∼ x − 1 , x + 1 ∼ n , n + 1 ∼ y − 1 , y + 1 ∼ n ∗ 2 1\sim x-1,x+1\sim n,n+1\sim y-1,y+1\sim n*2 1x1,x+1n,n+1y1,y+1n2

通过枚举 m m m以及乘法原理,我们可以得到这种情况的答案:

通过枚举mmm以及乘法原理,我们可以得到这种情况的答案:

图示:

a n s = ∑ i = 1 m f ( x − 1 , i ) ∗ f ( n − x , m − i + 1 ) ∗ f ( y − n − 1 , m − i + 1 ) ∗ f ( 2 ∗ n − y , i ) ans=\sum\limits_{i=1}^m f(x-1,i)*f(n-x,m-i+1)*f(y-n-1,m-i+1)*f(2*n-y,i) ans=i=1mf(x1,i)f(nx,mi+1)f(yn1,mi+1)f(2ny,i)

  • n < x < y n<x<y n<x<y x < y < = n x<y<=n x<y<=n

图示:

在这里插入图片描述注意到 x x x y y y是相等的,所以我们可以把区间 x ∼ y x\sim y xy合并成一个点。

又根据乘法原理,我们可以 O ( 1 ) O(1) O(1)计算出答案:

a n s = f ( n , m ) × f ( n + x − y , m ) ans=f(n,m)\times f(n+x-y,m) ans=f(n,m)×f(n+xy,m)

但问题来了:

如何O(1) 计 算 f 函 数 ? ? ? ? \color{red}\text{如何O(1)}计算f函数???? 如何O(1)f

我们之前提到过 f ( i , j ) f(i,j) f(i,j)就是 ( i + j − 1 j − 1 ) \dbinom{i+j-1}{j-1} (j1i+j1)所以求 f f f函数又转化为求组合数

而众所周知,

( n m ) = n ! m ! ( n − m ) ! \dbinom{n}{m}=\dfrac{n!}{m!(n-m)!} (mn)=m!(nm)!n!

所以,

f ( i , j ) = ( i + j − 1 ) ! i ! ( j − 1 ) ! ​ f(i,j)=\dfrac{(i+j-1)!}{i!(j-1)!}​ f(i,j)=i!(j1)!(i+j1)!

我们现在剩下的任务就是求 1 ! ∼ 200000 ! 1!\sim 200000! 1!200000!的逆元( n + m n+m n+m最大为 200000 200000 200000,当然你也可以只预处理到 n + m n+m n+m)。

接下来介绍两种求逆元的方法:

  • 无需理解,会逆元就行

学过都知道,一个数 x x x的逆元就是 x m o d − 2 x^{mod-2} xmod2(不会的记得这个公式就行了)

所以在循环的时候,直接每次利用求出的frafrafra数组(也就是阶乘)以及快速幂进行求逆元就行了

时间复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn)

代码片-1

void prework()
{
	fac[0]=inv[0]=1;
	for(int i=1;i<=200000;i++)fac[i]=(fac[i-1]*i)%mod;
	for(int i=1;i<=200000;i++)inv[i]=quick(fac[i],mod-2);
}

首先我们还需要求出frafrafra数组。

然后求出 i n v [ n + m ] inv[n+m] inv[n+m]

最后后从后往前循环,每次求 i n v [ i ] inv[i] inv[i](阶乘逆元)时,将 i n v [ i + 1 ] inv[i+1] inv[i+1]乘以 ( i + 1 ) (i+1) (i+1)即可得到 i n v [ i ] inv[i] inv[i]

证明?

( n + 1 ) ! × 1 n + 1 = n ! (n+1)!\times \dfrac{1}{n+1}=n! (n+1)!×n+11=n!

时间复杂度为 O ( n ) O(n) O(n)

代码片-2

void prework()
{
	fac[0]=inv[0]=1;
	for(int i=1;i<=200000;i++)fac[i]=(fac[i-1]*i)%mod;
	inv[200000]=quick(fac[200000],mod-2);
	for(int i=199999;i;i--)inv[i]=inv[i+1]*(i+1)%mod;
}

代码片-3

最后是 f f f函数的代码

LL Func(LL x,LL y)
{
	return fac[y+x-1]*inv[x]%mod*inv[y-1]%mod;
}

代码

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int mod=998244353;
const int Maxn=200001;
LL fac[Maxn],inv[Maxn],n,m,ans;
LL qp(LL a,LL b)
{
	LL xx=a,yy=1;
	while(b)
	{
		if(b&1) yy=yy*xx%mod;
		xx=xx*xx%mod;
		b/=2;
	}
	return yy;
}
LL Func(LL x,LL y)
{
	return fac[y+x-1]*inv[x]%mod*inv[y-1]%mod;
}
int main()
{
	LL x,y;
	fac[0]=inv[0]=1;
	for(int i=1;i<=200000;i++)fac[i]=(fac[i-1]*i)%mod;
	inv[200000]=qp(fac[200000],mod-2);
	for(int i=199999;i;i--)inv[i]=inv[i+1]*(i+1)%mod;
	scanf("%lld%lld%lld%lld",&m,&n,&x,&y);
	if(x<=n&&y>n)
	{
		for(int i=1;i<=m;i++)
		{
			ans=(ans+Func(x-1,i)*Func(n-x,m-i+1)%mod*Func(2*n-y,i)%mod*Func(y-n-1,m-i+1))%mod;
		}
	}else{
		ans=Func(n,m)*Func(n+x-y,m)%mod;
	}
	printf("%lld",ans);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值