洛谷 P6475 [NOI Online #2 入门组] 建设城市 题解--zhengjun
作者:互联网
一看,这个就是一个组合数学,如图所示
这样,很容易想到分类讨论,如果\(x,y\)在两侧和\(x,y\)在同侧。
如果是两侧的话,就可以枚举这两个位置的高度然后用组合数算出来就可以了。然后的话如果在同侧就不用管什么东西,把第\(x\)个位置到第\(y\)个位置的所有位置都是一样高的,就可以看成一个城市,剩下的左边\(x-1(x-n-1)\)个,右边\(n-y(2\times n-y)\)个,加上中间的一个就是\(x+n-y(\)两种刚好一样\()\),就直接算出来就可以了。
因为要组合数,所以我们一开始可以处理一下组合数,公式:\(C_n^m=C_{n-1}^m+C_n^{m-1}\)个,然后直接用就可以了,然后我们推一下有\(x\)个位置,可以选\(y\)种不同的高度的可能数,其实就是一个组合数\(C_y^{x+1}\)直接用即可。
复杂度\(O(nm)\),得分\(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\)分的做法,因为这个组合数十分不好,复杂度太高,于是我们想到了这个式子\(C_{n}^{m}=\frac{n!}{m!(n-m)!}\),那么我们就可以预处理出阶乘,可是这样是取模之后的结果,不能直接用来除,这里就要用到逆元的知识:
例如\(ax\equiv1\pmod{b}\),其实就是求\(a\)关于\(b\)\((b\)其实就是模数\()\)的逆元\(x\),因为\(x\equiv x+kb\pmod{b}\)
所以\(ax\equiv1\pmod{b}\)就相当于\(ax+by\equiv1\pmod{b}\)
我谔谔,这个不就是扩展欧几里得算法吗,直接求得\(x\)就好了,然后要注意最后要转换成正的。
逆元这个东西,其实是在取模运算中改变符号,就像实数取相反数和分数取倒数一样的,都是改变符号,一个数\(a\)关于\(b\)的逆元用\(inv(a,b)\)表示,所以原来的\(C_{n}^{m}=\frac{n!}{m!(n-m)!}\)就可以转换成\(C_{n}^{m}=n!\times inv(m!,mod)\times inv((n-m)!,mod)\bmod mod\),这样只要处理出这个\(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\ log n)\),已经可以过了。
代码
#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;
}
如果还觉得太慢,可以考虑线性求逆元(模数相同),用\(inv_i\)或\(i^{-1}\)表示\(i\)关于\(p\)的逆元,因为\(p\bmod i+\lfloor\dfrac{p}{i}\rfloor\times i=p\)
所以,设\(a=p\bmod i,b=\lfloor\dfrac{p}{i}\rfloor\)
则\(a+b\times i=p\)
\(a+b\times i\equiv0\pmod{p}\)
\(b\times i\equiv-a\pmod{p}\)
\(i\equiv-\dfrac{a}{b}\pmod{p}\)
\(i^{-1}\equiv-\dfrac{b}{a}=-b\times a^{-1}\pmod{p}\)
也就是\(i\)的逆元\(inv(i)\)就是\(-\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\)
于是就可以线性求出逆元了,初始化:\(inv_1=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)!\times n\)
所以\(n!^{-1}=(n-1)!^{-1}\times n^{-1}\)
然后,就可以先处理出\(1\)到\(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
标签:洛谷,--,题解,inv,times,yy,int,xx,mod 来源: https://www.cnblogs.com/A-zjzj/p/16365740.html