NOI Online #2 入門組第三題建設城市題解--zhengjun

一看,這個就是一個組合數學,如圖所示
A_zjzj
這樣,很容易想到分類討論,如果x,yx,y在兩側和x,yx,y在同側。

如果是兩側的話,就可以枚舉這兩個位置的高度然後用組合數算出來就可以了。然後的話如果在同側就不用管什麼東西,把第xx個位置到第yy個位置的所有位置都是一樣高的,就可以看成一個城市,剩下的左邊x1(xn1)x-1(x-n-1)個,右邊ny(2×ny)n-y(2\times n-y)個,加上中間的一個就是x+ny(x+n-y(兩種剛好一樣)),就直接算出來就可以了。

因爲要組合數,所以我們一開始可以處理一下組合數,公式:Cnm=Cn1m+Cnm1C_n^m=C_{n-1}^m+C_n^{m-1}個,然後直接用就可以了,然後我們推一下有xx個位置,可以選yy種不同的高度的可能數,其實就是一個組合數Cyx+1C_y^{x+1}直接用即可。

複雜度O(nm)O(nm),得分6060

代碼

#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;
}

然後,我們來談談100100分的做法,因爲這個組合數十分不好,複雜度太高,於是我們想到了這個式子Cnm=n!m!(nm)!C_{n}^{m}=\frac{n!}{m!(n-m)!},那麼我們就可以預處理出階乘,可是這樣是取模之後的結果,不能直接用來除,這裏就要用到逆元的知識:

例如ax1(modb)ax\equiv1\pmod{b},其實就是求aa關於bb(b(b其實就是模數))的逆元xx,因爲xx+kb(modb)x\equiv x+kb\pmod{b}

所以ax1(modb)ax\equiv1\pmod{b}就相當於ax+by1(modb)ax+by\equiv1\pmod{b}

我諤諤,這個不就是擴展歐幾里得算法嗎,直接求得xx就好了,然後要注意最後要轉換成正的。

逆元這個東西,其實是在取模運算中改變符號,就像實數取相反數和分數取倒數一樣的,都是改變符號,一個數aa關於bb的逆元用inv(a,b)inv(a,b)表示,所以原來的Cnm=n!m!(nm)!C_{n}^{m}=\frac{n!}{m!(n-m)!}就可以轉換成Cnm=n!×inv(m!,mod)×inv((nm)!,mod)modmodC_{n}^{m}=n!\times inv(m!,mod)\times inv((n-m)!,mod)\bmod mod,這樣只要處理出這個invinv函數就可以了(前面說過)

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 logn)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;
}

如果還覺得太慢,可以考慮線性求逆元(模數相同),用inviinv_ii1i^{-1}表示ii關於pp的逆元,因爲pmodi+pi×i=pp\bmod i+\lfloor\dfrac{p}{i}\rfloor\times i=p

所以,設a=pmodi,b=pia=p\bmod i,b=\lfloor\dfrac{p}{i}\rfloor

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

a+b×i0(modp)a+b\times i\equiv0\pmod{p}

b×ia(modp)b\times i\equiv-a\pmod{p}

iab(modp)i\equiv-\dfrac{a}{b}\pmod{p}

i1ba=b×a1(modp)i^{-1}\equiv-\dfrac{b}{a}=-b\times a^{-1}\pmod{p}

也就是ii的逆元inv(i)inv(i)就是pi×invpmodimodp=(ppi)×invpmodimodp-\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

於是就可以線性求出逆元了,初始化:inv1=1inv_1=1

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

然後,這題卻要維護一個階乘數組的逆元。

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

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

然後,就可以先處理出11nn的逆元,然後就可以遞推求出階乘數組的逆元了。

代碼

#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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章