Atcoder Grand Contest 001E BBQ Hard(组合意义转化,思维题)

Atcoder 题面传送门 & 洛谷题面传送门

Yet another 思维题……

注意到此题 \(n\) 数据范围很大,但是 \(a_i,b_i\) 数据范围很小,这能给我们什么启发呢?

观察题目所求的组合数的形式,我们可以联想到组合数的组合意义(qwq 似乎 AGC 很喜欢放组合意义的题?涨见识了/cy):\(\dbinom{x+y}{x}\) 为从 \((0,0)\) 出发,只能向上或向右走,到达 \((x,y)\) 的方案数。

于是此题可以转化为,对于 \(\forall i,j\) 求出 \((0,0)\) 出发,到达 \((a_i+a_j,b_i+b_j)\) 的方案数。

但是这样貌似还是不好求,于是考虑再进行一个转化,将坐标轴进行平移,即可转化为 \((-a_i,-b_i)\)\((a_j,b_j)\) 的方案数。

故本题等价于求计算两两点之间的路径条数总和。这个可以用一个简单的 \(dp\) 求出,\(dp_{i,j}\) 表示到达 \((x,y)\) 的路径条数,那么有个显然的转移方程 \(dp_{i,j}=dp_{i-1,j}+dp_{i,j-1}+[is_{i,j}]\),其中 \(is_{i,j}\) 表示 \((i,j)\) 是否为起点。这个 \(dp\) 显然可以在 \(\mathcal O(\max^2\{a_i\})\) 的时间内计算求得。最终答案即为 \(\sum\limits_{i=1}^ndp_{a_i,b_i}\)

还有个小问题,就是通过以上算法求得的答案为对于任意 \(i,j\)\(\dbinom{a_i+a_j+b_i+b_j}{a_i+a_j}\) 的和,而题目要求 \(i<j\),故需将求得的答案减去 \(i=j\) 部分的答案后再除以 \(2\)。至于怎样求 \(i=j\) 部分的答案……这个就不用说了吧,刚学 OI 的时候就会了(bushi)。

时间复杂度 \(\mathcal O(n+m^2)\),其中 \(m=\max a_i\)

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
	#define FILE_SIZE 1<<23
	char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
	inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
	inline void putc(char x){(*p3++=x);}
	template<typename T> void read(T &x){
		x=0;char c=getchar();T neg=0;
		while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
		while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
		if(neg) x=(~x)+1;
	}
	template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
	template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
	void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=2e5;
const int DELTA=2002;
const int MOD=1e9+7;
const int INV2=5e8+4;
int n,a[MAXN+5],b[MAXN+5],dp[DELTA*2+5][DELTA*2+5];
int fac[DELTA*4+5],ifac[DELTA*4+5];
void prework(int k){
	fac[0]=ifac[0]=ifac[1]=1;
	for(int i=2;i<=k;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=1;i<=k;i++) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i]*ifac[i-1]%MOD;
}
int binom(int x,int y){return 1ll*fac[x]*ifac[x-y]%MOD*ifac[y]%MOD;}
int main(){
	scanf("%d",&n);prework(DELTA<<2);int ans=0;
	for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]),dp[DELTA-a[i]][DELTA-b[i]]++;
	for(int i=1;i<=DELTA*2;i++) for(int j=1;j<=DELTA*2;j++) dp[i][j]=(dp[i][j]+(dp[i-1][j]+dp[i][j-1])%MOD)%MOD;
	for(int i=1;i<=n;i++) ans=(ans+dp[DELTA+a[i]][DELTA+b[i]])%MOD;
	for(int i=1;i<=n;i++) ans=(ans-binom(a[i]+a[i]+b[i]+b[i],a[i]+a[i])+MOD)%MOD;
	ans=1ll*ans*INV2%MOD;printf("%d\n",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/ET2006/p/agc001E.html