1. 程式人生 > 其它 >AT1983 [AGC001E] BBQ Hard(組合計數)

AT1983 [AGC001E] BBQ Hard(組合計數)

題意

\(n\) 個數對 \((a_i,b_i)\),求:

\(\sum_{i=1}^{n} \sum_{j=i+1}^{n} C_{a_i+b_i+a_j+b_j}^{a_i+a_j}\)

資料範圍

\(2 \leq N \leq 200000\)

\(1\leq a_i,b_i \leq 2000\)

思路

預處理出階層,直接列舉的時間複雜度為 \(O(n^2)\)。顯然需要更優的做法。

可以考慮題目要求的式子的幾何意義,\(C_{a_i+b_i+a_j+b_j}^{a_i+a_j}\) 可以看成是從直角座標系的原點 \((0,0)\),只向上或向右走,走到 \((a_i+a_j,b_i+b_j)\)

的方案數(因為只向上或向右走,需要走 \(a_i+a_j+b_i+b_j\) 步,而其中 \(a_i+a_j\) 步是要向右走的,列舉哪幾步向右走就是這個公式)。

但是這樣還是無法做到更優的複雜度。可以考慮進一步轉化為從 \((-a_i,-b_i)\) 走到 \((a_j,b_j)\) 的方案數,含義不變。對於每一個終點 \((a_i,b_i)\),都可以從所有的 \((-a_j,-b_j)\) 走過來,可以直接 \(O(\max (a_i+a_j)^2)\) dp 算出所有的答案(具體實現可以看程式碼)。這樣計算會多算 \(i \geq j\) 的方案,於是可以先減去從 \((-a_i,-b_i)\)

走到 \((a_i,b_i)\) 的方案數,最終再把答案除以 \(2\) (乘以 \(2\) 的逆元)。

最終的時間複雜度就是 \(O(n+4000^2)\)

code:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mod=1e9+7;
const int N=4040,NLC=2020,M=2e5+10;
int f[N][N],ans,n,inv[N<<1],fac[N<<1],infac[N<<1],a[M],b[M];
void init()
{
	fac[0]=fac[1]=inv[0]=inv[1]=infac[0]=infac[1]=1;
	for(int i=2;i<N<<1;i++)
	{
		fac[i]=1ll*fac[i-1]*i%mod;
		inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
		infac[i]=1ll*infac[i-1]*inv[i]%mod;
	}
}
int C(int n,int m){return 1ll*fac[n]*infac[m]%mod*infac[n-m]%mod;}
int main()
{
	init();
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]),f[NLC-a[i]][NLC-b[i]]++;//直接儲存負數會 RE,需要整體加上一個偏移量
	for(int i=1;i<N;i++)
	    for(int j=1;j<N;j++)
	        f[i][j]=(f[i][j]+f[i][j-1]+f[i-1][j])%mod;
	for(int i=1;i<=n;i++)
	{
		ans=(ans+f[NLC+a[i]][NLC+b[i]])%mod;
		ans=((ans-C(2*a[i]+2*b[i],2*a[i]))%mod+mod)%mod;
	}
	printf("%d\n",1ll*ans*inv[2]%mod);
	return 0;
}