洛谷 P6475 [NOI Online #2 入门组] 建设城市 题解--zhengjun

一看,这个就是一个组合数学,如图所示
A_zjzj
这样,很容易想到分类讨论,如果 x , y x,y x,y在两侧和 x , y x,y x,y在同侧。

如果是两侧的话,就可以枚举这两个位置的高度然后用组合数算出来就可以了。然后的话如果在同侧就不用管什么东西,把第 x x x个位置到第 y y y个位置的所有位置都是一样高的,就可以看成一个城市,剩下的左边 x − 1 ( x − n − 1 ) x-1(x-n-1) x1(xn1)个,右边 n − y ( 2 × n − y ) n-y(2\times n-y) ny(2×ny)个,加上中间的一个就是 x + n − y ( x+n-y( x+ny(两种刚好一样 ) ) ),就直接算出来就可以了。

因为要组合数,所以我们一开始可以处理一下组合数,公式: C n m = C n − 1 m + C n m − 1 C_n^m=C_{n-1}^m+C_n^{m-1} Cnm=Cn1m+Cnm1个,然后直接用就可以了,然后我们推一下有 x x x个位置,可以选 y y y种不同的高度的可能数,其实就是一个组合数 C y x + 1 C_y^{x+1} Cyx+1直接用即可。

复杂度 O ( n m ) O(nm) O(nm),得分 60 60 60

代码

#include<bits/stdc++.h>
#define ll long long
#define mod 998244353
using namespace std;
int n,m,x,y;
int t;
int f[5005][5005];
int main(){
	scanf("%d%d%d%d",&m,&n,&x,&y);
	for(int i=1;i<=n+2;i++){
		f[i][1]=1;
		for(int j=2;j<=m+1;j++){
			f[i][j]=(f[i-1][j]+f[i][j-1])%mod;
		}
	}
	if(x<=n&&y>n){
		int ans=0;
		for(int i=1;i<=m;i++){
			ans=(ans+((ll)f[x][i]*f[n-x+1][m-i+1]%mod*f[y-n][m-i+1]%mod*f[n*2-y+1][i]%mod))%mod;
		}
		printf("%d",ans);
	}
	else{
		printf("%d",(ll)f[n+1][m]*f[x+n-y+1][m]%mod);
	}
	return 0;
}

然后,我们来谈谈 100 100 100分的做法,因为这个组合数十分不好,复杂度太高,于是我们想到了这个式子 C n m = n ! m ! ( n − m ) ! C_{n}^{m}=\frac{n!}{m!(n-m)!} Cnm=m!(nm)!n!,那么我们就可以预处理出阶乘,可是这样是取模之后的结果,不能直接用来除,这里就要用到逆元的知识:

例如 a x ≡ 1 ( m o d b ) ax\equiv1\pmod{b} ax1(modb),其实就是求 a a a关于 b b b ( b (b (b其实就是模数 ) ) )的逆元 x x x,因为 x ≡ x + k b ( m o d b ) x\equiv x+kb\pmod{b} xx+kb(modb)

所以 a x ≡ 1 ( m o d b ) ax\equiv1\pmod{b} ax1(modb)就相当于 a x + b y ≡ 1 ( m o d b ) ax+by\equiv1\pmod{b} ax+by1(modb)

我谔谔,这个不就是扩展欧几里得算法吗,直接求得 x x x就好了,然后要注意最后要转换成正的。

逆元这个东西,其实是在取模运算中改变符号,就像实数取相反数和分数取倒数一样的,都是改变符号,一个数 a a a关于 b b b的逆元用 i n v ( a , b ) inv(a,b) inv(a,b)表示,所以原来的 C n m = n ! m ! ( n − m ) ! C_{n}^{m}=\frac{n!}{m!(n-m)!} Cnm=m!(nm)!n!就可以转换成 C n m = n ! × i n v ( m ! , m o d ) × i n v ( ( n − m ) ! , m o d )   m o d   m o d C_{n}^{m}=n!\times inv(m!,mod)\times inv((n-m)!,mod)\bmod mod Cnm=n!×inv(m!,mod)×inv((nm)!,mod)modmod,这样只要处理出这个 i n v inv inv函数就可以了(前面说过)

void exgcd(int a,int b,int &xx,int &yy){
	if(b==0){
		xx=1,yy=0;
		return;
	}
	exgcd(b,a%b,xx,yy);
	int t=xx;
	xx=yy;
	yy=t-a/b*yy;
}
int inv(int a){
	int xx,yy;
	exgcd(a,mod,xx,yy);
	return (xx%mod+mod)%mod;//转换成正的
}

复杂度 O ( m   l o g n ) O(m\ log n) O(m logn),已经可以过了。

代码

#include<bits/stdc++.h>
#define ll long long
#define mod 998244353
using namespace std;
int n,m,x,y;
int k[500001];
void exgcd(int a,int b,int &xx,int &yy){
	if(b==0){
		xx=1,yy=0;
		return;
	}
	exgcd(b,a%b,xx,yy);
	int t=xx;
	xx=yy;
	yy=t-a/b*yy;
}
int inv(int a){
	int xx,yy;
	exgcd(a,mod,xx,yy);
	return (xx%mod+mod)%mod;
}
int f(int xx,int yy){
	int a=inv(k[yy-1]),b=inv(k[xx]),p=k[xx+yy-1];
	return (ll)p*a%mod*b%mod;
}
int main(){
	scanf("%d%d%d%d",&m,&n,&x,&y);
	k[0]=1;
	for(int i=1;i<=500000;i++)k[i]=(ll)k[i-1]*i%mod;
	if(x<=n&&y>n){
		int ans=0;
		for(int i=1;i<=m;i++){
			ans=(ans+((ll)f(x-1,i)*f(n-x,m-i+1)%mod*f(y-n-1,m-i+1)%mod*f(n*2-y,i)%mod))%mod;
		}
		printf("%d",ans);
	}
	else{
		printf("%d",(ll)f(n,m)*f(x+n-y,m)%mod);
	}
	return 0;
}

如果还觉得太慢,可以考虑线性求逆元(模数相同),用 i n v i inv_i invi i − 1 i^{-1} i1表示 i i i关于 p p p的逆元,因为 p   m o d   i + ⌊ p i ⌋ × i = p p\bmod i+\lfloor\dfrac{p}{i}\rfloor\times i=p pmodi+ip×i=p

所以,设 a = p   m o d   i , b = ⌊ p i ⌋ a=p\bmod i,b=\lfloor\dfrac{p}{i}\rfloor a=pmodi,b=ip

a + b × i = p a+b\times i=p a+b×i=p

a + b × i ≡ 0 ( m o d p ) a+b\times i\equiv0\pmod{p} a+b×i0(modp)

b × i ≡ − a ( m o d p ) b\times i\equiv-a\pmod{p} b×ia(modp)

i ≡ − a b ( m o d p ) i\equiv-\dfrac{a}{b}\pmod{p} iba(modp)

i − 1 ≡ − b a = − b × a − 1 ( m o d p ) i^{-1}\equiv-\dfrac{b}{a}=-b\times a^{-1}\pmod{p} i1ab=b×a1(modp)

也就是 i i i的逆元 i n v ( i ) inv(i) inv(i)就是 − ⌊ p i ⌋ × i n v p   m o d   i   m o d   p = ( p − ⌊ p i ⌋ ) × i n v p   m o d   i   m o d   p -\lfloor\dfrac{p}{i}\rfloor\times inv_{p\bmod i}\bmod p=(p-\lfloor\dfrac{p}{i}\rfloor)\times inv_{p\bmod i}\bmod p ip×invpmodimodp=(pip)×invpmodimodp

于是就可以线性求出逆元了,初始化: i n v 1 = 1 inv_1=1 inv1=1

inv[1]=1;
for(int i=2;i<=n;i++)inv[i]=(long long)(p-p/i)*inv[p%i]%p;

然后,这题却要维护一个阶乘数组的逆元。

n ! = ( n − 1 ) ! × n n!=(n-1)!\times n n!=(n1)!×n

所以 n ! − 1 = ( n − 1 ) ! − 1 × n − 1 n!^{-1}=(n-1)!^{-1}\times n^{-1} n!1=(n1)!1×n1

然后,就可以先处理出 1 1 1 n n n的逆元,然后就可以递推求出阶乘数组的逆元了。

代码

#include<bits/stdc++.h>
#define f(x,y) ((long long)k[x+y-1]*invk[y-1]%mod*invk[x]%mod)
#define ll long long
#define mod 998244353
using namespace std;
int n,m,x,y;
int k[200001];
int inv[200001];
int invk[200001];
int main(){
	scanf("%d%d%d%d",&m,&n,&x,&y);
	register int i,ans=0;
	k[0]=inv[1]=invk[0]=1;
	for(i=2;i<=n+m;i++)inv[i]=((ll)mod-mod/i)*inv[mod%i]%mod;//处理1到n的逆元
	for(i=1;i<=n+m;i++)k[i]=(ll)k[i-1]*i%mod;//处理阶乘
	for(i=1;i<=n+m;i++)invk[i]=(ll)invk[i-1]*inv[i]%mod;//处理阶乘数组的逆元
	if(x<=n&&y>n){
		for(i=1;i<=m;i++)ans=(ans+((ll)f(x-1,i)*f(n-x,m-i+1)%mod*f(y-n-1,m-i+1)%mod*f(n*2-y,i)%mod))%mod;
		printf("%d",ans);
	}
	else{
		printf("%d",(ll)f(n,m)*f(x+n-y,m)%mod);
	}
	return 0;
}

谢谢–zhengjun

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A_zjzj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值