Codeforces 840C - On the Bench(dp/容斥原理)

Codeforces 题目传送门 & 洛谷题目传送门

这是一道 *2500 的 D1C,可个人认为难度堪比某些 *2700 *2800。

不过嘛,*2500 终究还是 *2500,还是被我自己想出来了

双倍经验 P4448 [AHOI2018初中组]球球的排列 哦

u1s1 其实一年以前做过 P4448,不过好像直到我 AC 这道题之后才发现这俩题一模一样,并且似乎这次用的方法和上次还不太一样

跑题了跑题了

首先有个性质:\(\forall a,b,c\in\mathbb{N}^+\)\(ab,bc\) 均为完全平方数,\(ac\) 也是完全平方数,因为假设 \(ab=x^2,bc=y^2\),则 \(ac=\dfrac{a^2b^2c^2}{ab·bc}=(\dfrac{abc}{xy})^2\),也就是说 \(ac\) 是一个有理数的平方,而又显然 \(ac\in\mathbb{N}^+\),故 \(ac\) 为完全平方数。

考虑 \(\forall i,j\in[1,n],i\ne j\),若 \(a_ia_j\) 为完全平方数,就在 \(i,j\) 之间连一条边,那么根据之前的推论,若 \((i,j),(j,k)\) 之间均存在边,那么 \((i,k)\) 间也存在边,也就是说得到的图是一个个团的并集

考虑将同一个团中的点染上同一个颜色,于是原题等价于给出 \(k\) 种颜色,第 \(i\) 种颜色的球有 \(c_i\) 个(每种颜色的球互不相同),其中 \(\sum\limits_{i=1}^kc_i=n\),求将这 \(n\) 个球排成一行,其中相邻两个球颜色不同的方案数。

考虑 \(dp\)\(dp_{i,j}\) 表示将前 \(i\) 种颜色的球排成一行,其中有 \(j\) 个相邻位置颜色相同的方案数。

考虑从 \(dp_i\) 转移到 \(dp_{i+1}\),我们枚举第 \(i+1\) 种颜色分成了多少组,设为 \(x\),再枚举这 \(x\) 组中有多少组摆在了原本颜色相同的两球之间,设为 \(y\),那么这样一来会少掉 \(y\) 个颜色相同的相邻位置,但同时由于 \(i+1\) 种颜色分为了 \(x\) 组,又会多出 \(c_{i+1}-x\) 对颜色相同的相邻位置。算下这样划分的方案数,设 \(s_{i,j}\) 表示将 \(i\) 个不同的球划分成 \(j\) 组,组内有序但组与组之间无序的方案数,那么将第 \(i+1\) 种颜色的球划分为 \(x\) 组的方案数为 \(s_{c_{i+1},x}\),从 \(x\) 组中选出 \(y\) 组的方案数为 \(\dbinom{x}{y}\),这 \(y\) 组进一步“消灭”颜色相同的位置的方案数为 \(k^{\underline{y}}\),将剩余的 \(x-y\) 组填入剩余的 \((\sum\limits_{t=1}^ic_t)+1\) 个空隙中的方案数为 \(((\sum\limits_{t=1}^ic_t)+1)^{\underline{x-y}}\),故我们有状态转移方程式 \(dp_{i,j}\times s_{c_{i+1},x}\times \dbinom{x}{y}\times k^{\underline{y}}\times ((\sum\limits_{t=1}^ic_t)+1)^{\underline{x-y}}\rightarrow dp_{i+1}{j-y+c_{i+1}-x}\)。最终答案即为 \(dp_{k,0}\)

最后考虑怎样求 \(s_{i,j}\),考虑最后一个球的摆法,若它单独成一组,则 \(dp_{i,j}\leftarrow dp_{i-1,j-1}\),若它与前面的求合并为一组,则它有 \(i-1+j\) 个空隙可以插入,\(dp_{i,j}\leftarrow dp_{i-1,j}\times(i+1-j)\)

算下时间复杂度,乍一看四重循环,\(n^4\),但实际上 \(\sum\limits_{i=1}^kc_i=n\),故复杂度是 \(n^3\) 的,可以通过此题。

#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=300;
const int MOD=1e9+7;
int n,a[MAXN+5],f[MAXN+5];
bool issqr(ll x){int _sqrt=sqrt(x);return (1ll*_sqrt*_sqrt==x);}
int find(int x){return (!f[x])?x:f[x]=find(f[x]);}
void merge(int x,int y){x=find(x);y=find(y);if(x^y) f[x]=y;}
int siz[MAXN+5],b[MAXN+5],bn=0,s[MAXN+5][MAXN+5];
int dp[MAXN+5][MAXN+5],fac[MAXN+5],ifac[MAXN+5];
void init_fac(int n){
	fac[0]=ifac[0]=ifac[1]=1;
	for(int i=2;i<=n;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD;
	for(int i=1;i<=n;i++) ifac[i]=1ll*ifac[i]*ifac[i-1]%MOD;
	s[0][0]=1;
	for(int i=1;i<=n;i++) for(int j=1;j<=i;j++){
		s[i][j]=(s[i-1][j-1]+1ll*(i-1+j)*s[i-1][j])%MOD;
	}
}
int binom(int x,int y){
	if(x<0||y<0||x<y) return 0;
	return 1ll*fac[x]*ifac[x-y]%MOD*ifac[y]%MOD;
}
int main(){
	scanf("%d",&n);init_fac(n+1);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) if(issqr(1ll*a[i]*a[j]))
		merge(i,j);
	for(int i=1;i<=n;i++) siz[find(i)]++;
	for(int i=1;i<=n;i++) if(find(i)==i) b[++bn]=siz[i];
//	for(int i=1;i<=bn;i++) printf("%d\n",b[i]);
	dp[0][0]=1;int sum=0;
	for(int i=0;i<bn;i++){
		for(int j=0;j<=sum-i;j++){
			for(int k=1;k<=b[i+1];k++){
				int ways=s[b[i+1]][k];
				for(int l=0;l<=min(j,k);l++){
					int ways2=1ll*binom(k,l)*fac[l]%MOD*binom(j,l)%MOD;
					ways2=1ll*ways2*binom(sum+1-j,k-l)%MOD*fac[k-l]%MOD;
					ways2=1ll*ways2*ways%MOD;ways2=1ll*dp[i][j]*ways2%MOD;
//					printf("%d %d %d %d %d\n",i,j,k,l,ways2);
					dp[i+1][j-l+b[i+1]-k]=(dp[i+1][j-l+b[i+1]-k]+ways2)%MOD;
				}
			}
		}
		sum+=b[i+1];
	}
//	for(int i=1;i<=bn;i++) for(int j=0;j<=n;j++) printf("%d %d %d\n",i,j,dp[i][j]);
	printf("%d\n",dp[bn][0]);
	return 0;
}

事实上本题还有更优秀的做法

下面就是我没想到的部分了

我们考虑将每种颜色定义一个 \(b_i\) 表示将这种颜色的球强制分为 \(b_i\) 组并要求同一组的球必须绑在一起。

考虑对于固定的 \(b_1,b_2,\dots,b_k\) 怎样计算这样填的方案数,首先对于每种颜色,将其分为 \(b_i\) 组的方案数为 \(c_i!\dbinom{c_i-1}{b_i-1}\times\),其次将这捆绑后的 \(B=\sum\limits_{i=1}^kb_i\) 组排列起来的方案数为 \(\dfrac{B!}{\prod\limits_{i=1}^kb_i!}\),故总方案数为 \(\prod\limits_{i=1}^k(c_i!\dbinom{c_i-1}{b_i-1})·\dfrac{B!}{\prod\limits_{i=1}^kb_i!}=B!\prod\limits_{i=1}^kc_i!·\prod\limits_{i=1}^k\dbinom{c_i-1}{b_i-1}\dfrac{1}{b_i!}\),我们考虑枚举 \(B\),那显然前两项都是常数,只需考虑第二个 \(\prod\) 中的东西就行了。

还是考虑 \(dp\)\(dp_{i,j}\) 表示考虑到前 \(i\) 个球,它们的 \(b_i\) 加起来等于 \(j\)\(\prod\limits_{i=1}^k\dbinom{c_i-1}{b_i-1}\dfrac{1}{b_i!}\) 的和。

转移就枚举 \(b_i=l\),那么显然有 \(dp_{i,j}=\sum dp_{i-1,j-l}\dbinom{c_i-1}{l-1}\dfrac{1}{l!}\)

那么最终 \(\sum\limits_{i=1}^k=B\) 的答案即为 \(dp_{k,B}\)

但是这样计算有个问题就是会重复计算,比方说排列方式为 1 1 2 2,那么它在 \(dp_{2,3},dp_{2,4}\) 中都会被计算,不过这个问题很容易解决,直接容斥原理一下就行了,故最终答案为 \(\sum\limits_{i=k}^ndp_{k,B}\times(-1)^{n-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=300;
const int MOD=1e9+7;
int n,a[MAXN+5],f[MAXN+5];
bool issqr(ll x){int _sqrt=sqrt(x);return (1ll*_sqrt*_sqrt==x);}
int find(int x){return (!f[x])?x:f[x]=find(f[x]);}
void merge(int x,int y){x=find(x);y=find(y);if(x^y) f[x]=y;}
int siz[MAXN+5],b[MAXN+5],bn=0;
int dp[MAXN+5][MAXN+5],fac[MAXN+5],ifac[MAXN+5];
void init_fac(int n){
	fac[0]=ifac[0]=ifac[1]=1;
	for(int i=2;i<=n;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD;
	for(int i=1;i<=n;i++) ifac[i]=1ll*ifac[i]*ifac[i-1]%MOD;
}
int binom(int x,int y){
	if(x<0||y<0||x<y) return 0;
	return 1ll*fac[x]*ifac[x-y]%MOD*ifac[y]%MOD;
}
int main(){
	scanf("%d",&n);init_fac(n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) if(issqr(1ll*a[i]*a[j]))
		merge(i,j);
	for(int i=1;i<=n;i++) siz[find(i)]++;
	for(int i=1;i<=n;i++) if(find(i)==i) b[++bn]=siz[i];
	dp[0][0]=1;
	for(int i=1;i<=bn;i++) for(int j=1;j<=n;j++) for(int k=1;k<=min(j,b[i]);k++){
		dp[i][j]=(dp[i][j]+1ll*dp[i-1][j-k]*binom(b[i]-1,k-1)%MOD*ifac[k]%MOD)%MOD;
	} int ans=0,mul=1;for(int i=1;i<=bn;i++) mul=1ll*mul*fac[b[i]]%MOD;
	for(int i=bn;i<=n;i++){
		if((n-i)&1) ans=(ans-1ll*dp[bn][i]*fac[i]%MOD*mul%MOD+MOD)%MOD;
		else ans=(ans+1ll*dp[bn][i]*fac[i]%MOD*mul%MOD)%MOD;
	} printf("%d\n",ans);
	return 0;
}
posted @ 2021-03-11 21:33  tzc_wk  阅读(83)  评论(0)    收藏  举报