AGC036C GP 2

有个一开始全\(0\)的数组,每次选择两个不同的位置,分别\(+1\)\(+2\)

问操作恰好\(m\)次之后可能形成的不同的数组的方案数。

\(n\le 10^6,m\le 5*10^5\)


考虑最终形成的数组为\(a_i\)。如果能够将其表示成\(x_i+2y_i\),那么有:\(\sum x_i=\sum y_i=m\)

分析怎样的\((x_i,y_i)\)有解:相当于每次选择\(i\neq j\),使得\(x_i,y_j\)各减一,最终能都减到\(0\)

显然的必要条件:\(x_i\le \sum_{j\neq i}y_j=m-y_i\)。于是有\(x_i+y_i\le m\)。可证这也是充分条件:

忽略所有\(x_i=y_i=0\)的点。

边界情况:\(n=2\)。此时\(x_1+y_1=x_2+y_2=m\),因为\(x_1+x_2=y_1+y_2=m\),可以推出\(x_1=y_2,x_2=y_1\),有解。

找到个\(x_i+y_i\)最大的,对于\(x_i,y_i\)中的较大者,找到\(y_j\)\(x_j\)\(j\neq i\)),两者各减一。(一定能找到:不妨设\(x_i\ge y_i\),如果找不到\(y_j>0\),则\(y_i=m\),此时\(x_i+y_i>m\)

因为\(x_i+y_i=m\)不超过两个(否则总和大于\(2m\)),所以搞完之后仍然满足\(x_i'+y_i'\le m'\)

归纳得证。

接下来问题是对于\(a_i\)找到合适的\(y_i\)。首先有\(\sum a_i=3m\),然后关于\(y_i\)的条件:

  1. \(\sum y_i=m\)
  2. \(a_i-2y_i+y_i\le m\)
  3. \(2y_i\le a_i\)

可得\(\max(a_i-m,0)\le y_i\le \lfloor\frac{a_i}{2}\rfloor\)。然后得到\(\sum y_i\)的上下界,从而\(\sum \max(a_i-m,0)\le m\le \sum\lfloor\frac{a_i}{2}\rfloor\)

整理得\(\sum a_i=3m,\sum (a_i\mod 2)\le m,\sum \max(a_i-m,0)\le m\),充分必要。

继续分析:当满足\(\sum a_i=3m\)时,找到最大值次大值\(fir,sec\),那么第三条限制即\(\max(fir-m,0)+\max(sec-m,0)\le m\),将\(\max\)拆开,发现其等价于\(fir\le 2m\)。所以把第三个条件换成\(\max a_i\le 2m\)

然后就可以计数啦。列出式子:

\[F=\sum_{i=0}^mx^{2i},G=\sum_{i=0}^{m-1}x^{2i+1}=x(F-x^{2m})\\ ans=[x^{3n}y^m]\frac{1}{1-y}(F+Gy)^n\\ \]

经过简单的推式子过程,就可以得到\(O(n+m)\)的做法。


using namespace std;
#include <bits/stdc++.h>
#define N 1000005
#define mo 998244353
#define ll long long 
ll qpow(ll x,ll y=mo-2){
	ll r=1;
	for (;y;y>>=1,x=x*x%mo)
		if (y&1)
			r=r*x%mo;
	return r;
}
int n,m;
ll fac[N*3],ifac[N*3];
void initC(int n){
	fac[0]=1;
	for (int i=1;i<=n;++i)
		fac[i]=fac[i-1]*i%mo;
	ifac[n]=qpow(fac[n]);
	for (int i=n-1;i>=0;--i)
		ifac[i]=ifac[i+1]*(i+1)%mo;
}
ll C(int m,int n){
	if (m<n) return 0;
	return fac[m]*ifac[n]%mo*ifac[m-n]%mo;
}
ll calc(int n,int k){
	if (k&1) return 0;
	k>>=1;
	return (C(k+n-1,n-1)-n*C(k-(m+1)+n-1,n-1))%mo;
}
int main(){
//	freopen("in.txt","r",stdin);
	scanf("%d%d",&n,&m);
	initC(n+m*3/2);
	ll ans=0;
	for (int j=0;j<=min(n,m);++j)
		ans=(ans+C(n,j)*(calc(n,3*m-j)-calc(n-1,m-j)*j%mo))%mo;
	ans=(ans+mo)%mo;
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-05-07 20:43  jz_597  阅读(36)  评论(0编辑  收藏  举报