题目描述
输入格式
第一行一个整数n(n≤200000)。
接下来n行,每行两个整数Ai和Bi(Ai,Bi≤3000)。
输出格式
由于实际答案可能很大,你只需要输出E*C(n,2)的值对998244353取余的结果,其中E表示期望值的准确值。
样例输入40 01 10 11 0
样例输出12
样例解释:取一二盒子:{br},{rb} 2种;取一三盒子:{r} 1种;取一四盒子:{b} 1种;取二三盒子:{brr},{rbr} ,{rrb} 3种;取二四盒子:{bbr},{brb},{rbb} 3 种;取三四盒子:{br} ,{rb} 2种;共12种。
来源 by azui
题解,来自azui:E*C(n,2)的值就是总情况数。转换思路,将小球模型转化为网格路径模型来看待。一共有a个红球,b个蓝球,放在一排的方案数等于a行b列的方格沿着右、下两个方向从左上角走到右下角的方案数。如果是两个盒子 i 和 j,那就是坐标系上从(-ai,-bi)走到(aj,bj)的方案数(最少步数)。将每个盒子拆成(ai,bi)和(-ai,-bi)两个点。现在考虑从第三象限中任意一点走到第一象限中任意一点的方案数之和(记为 X)。X 的求法显然,只需一个6000*6000的网格dp即可做到。其与答案的出入为:增加了 j≤i 的非法情况。我们可以O(n)减去i和j相等的情况,就只剩下j<i的非法情况了,显然由于对称性,只需除以2即可(乘以 2 的逆元)。
#include<cstdio> const int Mod=998244353; const int T=3000; const int N=6000; const int M=200000; void Getin( int &shu ) {//最好加上读入优化, 以及别开long long, 改用1ll char c; int f=1; shu=0; for( c=getchar(); c<'0' || c>'9'; c=getchar() ) if( c=='-' ) f=-1; for( ; c>='0' && c<='9'; c=getchar() ) shu=shu*10+c-'0'; shu*=f; } int fac[M+5], inv[M+5]; void Pre() { fac[0]=fac[1]=inv[0]=inv[1]=1; for( int i=2; i<=M; i++ ) { fac[i]=1ll*i*fac[i-1]%Mod; inv[i]=1ll*( Mod-Mod/i )*inv[Mod%i]%Mod; //线性求逆元, 可参考http://blog.miskcoo.com/2014/09/linear-find-all-invert } for( int i=2; i<=M; i++ ) inv[i]=1ll*inv[i]*inv[i-1]%Mod; //阶乘的逆元等于逆元的阶乘: inv(4!)==inv(1)*inv(2)*inv(3)*inv(4) } int C( int m, int r ) { return 1ll*fac[m]*inv[r]%Mod*inv[m-r]%Mod; } int n, sum, ans, a, b; int dp[N+5][N+5], cnt[T+5][T+5]; int main() { Pre(); Getin(n); for( int i=1; i<=n; i++ ) { Getin(a); Getin(b); sum=( sum+C( a+a+b+b, a+a ) )%Mod;//记录i==j的情况 dp[T-a][T-b]++;//将原点(0,0)平移至(3000,3000), (-3000,-3000)变为(0,0) cnt[a][b]++;//记重 } for( int i=0; i<=N; i++ ) for( int j=0; j<=N; j++ ) { if(i) dp[i][j]=( dp[i][j]+dp[i-1][j] )%Mod; if(j) dp[i][j]=( dp[i][j]+dp[i][j-1] )%Mod; if( i>=T && j>=T && cnt[i-T][j-T] ) ans=( ans+1ll*dp[i][j]*cnt[i-T][j-T]%Mod )%Mod; } ans=( ans-sum+Mod)%Mod; ans=1ll*ans*inv[2]%Mod; printf( "%d\n", ans ); return 0; }