一看,這個就是一個組合數學,如圖所示
這樣,很容易想到分類討論,如果在兩側和在同側。
如果是兩側的話,就可以枚舉這兩個位置的高度然後用組合數算出來就可以了。然後的話如果在同側就不用管什麼東西,把第個位置到第個位置的所有位置都是一樣高的,就可以看成一個城市,剩下的左邊個,右邊個,加上中間的一個就是兩種剛好一樣,就直接算出來就可以了。
因爲要組合數,所以我們一開始可以處理一下組合數,公式:個,然後直接用就可以了,然後我們推一下有個位置,可以選種不同的高度的可能數,其實就是一個組合數直接用即可。
複雜度,得分
代碼
#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;
}
然後,我們來談談分的做法,因爲這個組合數十分不好,複雜度太高,於是我們想到了這個式子,那麼我們就可以預處理出階乘,可是這樣是取模之後的結果,不能直接用來除,這裏就要用到逆元的知識:
例如,其實就是求關於其實就是模數的逆元,因爲
所以就相當於
我諤諤,這個不就是擴展歐幾里得算法嗎,直接求得就好了,然後要注意最後要轉換成正的。
逆元這個東西,其實是在取模運算中改變符號,就像實數取相反數和分數取倒數一樣的,都是改變符號,一個數關於的逆元用表示,所以原來的就可以轉換成,這樣只要處理出這個函數就可以了(前面說過)
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;//轉換成正的
}
複雜度,已經可以過了。
代碼
#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[1]=1;
for(int i=2;i<=n;i++)inv[i]=(long long)(p-p/i)*inv[p%i]%p;
然後,這題卻要維護一個階乘數組的逆元。
所以
然後,就可以先處理出到的逆元,然後就可以遞推求出階乘數組的逆元了。
代碼
#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;
}