CF840C On the Bench 和 LG6151 青春猪头少年不会梦到兔女郎学姐 On the Bench 青春猪头少年不会梦到兔女郎学姐

CF840C On the Bench 和 LG6151 青春猪头少年不会梦到兔女郎学姐
On the Bench
青春猪头少年不会梦到兔女郎学姐

(N)种球,第(i)种球有(A_i)个,求排列方式,使得相同种类的球不相邻。

每种球内部是否区分只是答案后面是否有个系数的差别,不用纠结。

(sum_i A_i ≤ 3000)

老做法

运用插空法最好可以做到 (O(n^3))。但是这个做法已经过时了。

Atcoder Typical DP Contest:O用的就是这个做法。

题解

限制是相邻两个球不能相同。当违反限制的时候,可以看成相邻两个球粘在了一起。

对于第(i)种球,如果违反了(j(0 ≤ j < A_i))个限制,就相当于有(j)对相邻的球被粘在了一起,方案数是 (inom{A_i−1}{j}) ,带上容斥系数就是((−1)^j),这时候可以看成有(A_i − j)个球拿出去任意排列。

用背包DP把对每种球的容斥过程合并到一起即可。时间复杂度是(O((sum_i A_i)^2))的。

也可以用多项式优化做到更好的复杂度。

CO int N=300+10;
int fac[N],ifac[N];
int val[N],cnt[N];
int dp[N][N];

IN int C(int n,int m){
	return mul(fac[n],mul(ifac[m],ifac[n-m]));
}
int main(){
	int n=read<int>();
	fac[0]=1;
	for(int i=1;i<=n;++i) fac[i]=mul(fac[i-1],i);
	ifac[n]=fpow(fac[n],mod-2);
	for(int i=n-1;i>=0;--i) ifac[i]=mul(ifac[i+1],i+1);
	for(int i=1;i<=n;++i){
		read(val[i]);
		bool flag=0;
		for(int j=1;j<i;++j){
			int64 s=(int64)val[i]*val[j],t=sqrt(s);
			if(t*t==s){
				++cnt[j];
				flag=1;break;
			}
		}
		if(!flag) ++cnt[i];
	}
	int m=0;
	for(int i=1;i<=n;++i)if(cnt[i]) cnt[++m]=cnt[i];
	dp[0][0]=1;
	int s=0;
	for(int i=1;i<=m;++i){
		for(int j=0;j<=s;++j)if(dp[i-1][j])
			for(int k=1;k<=cnt[i];++k){
				if((cnt[i]-k)%2==0)
					dp[i][j+k]=add(dp[i][j+k],mul(dp[i-1][j],mul(C(cnt[i]-1,k-1),C(j+k,k))));
				else
					dp[i][j+k]=add(dp[i][j+k],mod-mul(dp[i-1][j],mul(C(cnt[i]-1,k-1),C(j+k,k))));
			}
				
		s+=cnt[i];
	}
	int ans=0;
	for(int j=0;j<=s;++j) ans=add(ans,dp[m][j]);
	for(int i=1;i<=m;++i) ans=mul(ans,fac[cnt[i]]);
	printf("%d
",ans);
	return 0;
}

青春猪头少年不会梦到兔女郎学姐

(N)种球,第(i)种球有(A_i)个。

对于一个序列,把它看成首尾相连的。一个序列的权值定义为每个极大相同颜色连续段长度的乘积。

求所有序列的权值和,对(998244353)取模。

(sum_i A_i ≤ 2 × 10^5)

题解

仓鼠《杂题选讲》。

想象这么一种暴力。假如枚举每种颜色最后分段是什么样的,那么可以直接用前面说的容斥做法统计方案数,乘上这种分段带来的价值加到答案里面去。

实际上可以把每种暴力的结果合并到一起,丢到后面的容斥的过程里面去。

先优化暴力的过程,数量为(A_i)的物品分成(n)段的贡献和用之前说的插板法计算。相当于每一段要选一个代表点。

然后用FFT计算出,每个分成(n)段的情况在容斥的时候又被分成了(m)段的方案数。

直接用分治FFT把容斥的情况合并到一起。

注意需要处理一个首尾成环的情况,可以考虑加一维表示目前有没有确定开头的颜色。

对于开头的颜色,需要特殊处理贡献。相当于第一段独立重复选两次代表点。

时间复杂度 (O(nlog^2 n))

CO int N=262144;
int omg[2][N],rev[N];

void NTT(poly&a,int dir){
	int lim=a.size(),len=log2(lim);
	for(int i=0;i<lim;++i) rev[i]=rev[i>>1]>>1|(i&1)<<(len-1);
	for(int i=0;i<lim;++i)if(i<rev[i]) swap(a[i],a[rev[i]]);
	for(int i=1;i<lim;i<<=1)
		for(int j=0;j<lim;j+=i<<1)for(int k=0;k<i;++k){
			int t=mul(omg[dir][N/(i<<1)*k],a[j+i+k]);
			a[j+i+k]=add(a[j+k],mod-t),a[j+k]=add(a[j+k],t);
		}
	if(dir==1){
		int ilim=fpow(lim,mod-2);
		for(int i=0;i<lim;++i) a[i]=mul(a[i],ilim);
	}
}
poly operator*(poly a,poly b){
	int n=a.size()+b.size()-1,lim=1<<(int)ceil(log2(n));
	a.resize(lim),NTT(a,0);
	b.resize(lim),NTT(b,0);
	for(int i=0;i<lim;++i) a[i]=mul(a[i],b[i]);
	NTT(a,1),a.resize(n);
	return a;
}
poly operator+(poly a,poly b){
	int n=max(a.size(),b.size());
	a.resize(n),b.resize(n);
	for(int i=0;i<n;++i) a[i]=add(a[i],b[i]);
	return a;
}

int fac[N],ifac[N];
poly f[N],g[N];

IN int C(int n,int m){
	if(m<0 or m>n) return 0;
	return mul(fac[n],mul(ifac[m],ifac[n-m]));
}
pair<poly,poly> solve(int l,int r){
	if(l==r) return {f[l],g[l]};
	int mid=(l+r)>>1;
	pair<poly,poly> left=solve(l,mid),right=solve(mid+1,r);
	return {left.first*right.first,left.first*right.second+left.second*right.first};
}
int main(){
	omg[0][0]=1,omg[0][1]=fpow(3,(mod-1)/N);
	omg[1][0]=1,omg[1][1]=fpow(omg[0][1],mod-2);
	for(int i=2;i<N;++i){
		omg[0][i]=mul(omg[0][i-1],omg[0][1]);
		omg[1][i]=mul(omg[1][i-1],omg[1][1]);
	}
	fac[0]=1;
	for(int i=1;i<N;++i) fac[i]=mul(fac[i-1],i);
	ifac[N-1]=fpow(fac[N-1],mod-2);
	for(int i=N-2;i>=0;--i) ifac[i]=mul(ifac[i+1],i+1);
	
	int n=read<int>();
	if(n==1){
		printf("%d
",read<int>());
		return 0;
	}
	
	for(int i=1;i<=n;++i){
		int c=read<int>();
		f[i].resize(c+1),g[i].resize(c+1);
		for(int j=1;j<=c;++j){
			f[i][j]=C(c+j-1,2*j-1);
			g[i][j]=add(f[i][j],mul(2,C(c+j-1,2*j)));
		}
//		cerr<<"f=";
//		for(int j=1;j<=c;++j) cerr<<" "<<f[i][j];
//		cerr<<endl;
//		cerr<<"g=";
//		for(int j=1;j<=c;++j) cerr<<" "<<g[i][j];
//		cerr<<endl;
		
		poly h(c+1);
		for(int j=1;j<=c;++j) f[i][j]=mul(f[i][j],fac[j-1]);
		for(int j=0;j<=c;++j) h[j]=j%2==1?mod-ifac[j]:ifac[j];
		reverse(h.begin(),h.end());
		f[i]=f[i]*h;
		for(int j=0;j<=c;++j) f[i][j]=f[i][j+c];
		f[i].resize(c+1);
		f[i][0]=0;
		for(int j=1;j<=c;++j) f[i][j]=mul(f[i][j],ifac[j-1]);
		
		for(int j=1;j<=c;++j) g[i][j]=mul(g[i][j],fac[j]);
		for(int j=0;j<=c;++j) h[j]=j%2==1?mod-ifac[j]:ifac[j];
		reverse(h.begin(),h.end());
		g[i]=g[i]*h;
		for(int j=0;j<=c;++j) g[i][j]=g[i][j+c];
		g[i].resize(c+1);
		g[i][0]=0;
		for(int j=1;j<=c;++j) g[i][j]=mul(g[i][j],ifac[j]);
		
//		cerr<<"nf=";
//		for(int j=1;j<=c;++j) cerr<<" "<<f[i][j];
//		cerr<<endl;
//		cerr<<"ng=";
//		for(int j=1;j<=c;++j) cerr<<" "<<g[i][j];
//		cerr<<endl;
		
		for(int j=1;j<=c;++j){
			f[i][j]=mul(f[i][j],ifac[j]);
			g[i][j]=mul(g[i][j],ifac[j-1]);
		}
	}
	
	poly res=solve(1,n).second;
	int ans=0;
	for(int i=n;i<(int)res.size();++i)
		ans=add(ans,mul(res[i],fac[i-1]));
	printf("%d
",ans);
	return 0;
}