[AHOI2018初中组]球球的排列

IX.[AHOI2018初中组]球球的排列

论DP的百种用法之一

因为DP必须有一种全面的状态,但是这道题……似乎排列等等问题都不是DP擅长处理的地方。

首先分析性质。我们发现,这种不能放在一起的关系具有传递性。因为如果xy=a2,xz=b2,那么yz=(xy)(yz)x2=a2b2x2=(abx)2

具有传递性的话,我们就会发现,所有不能放在一起的位置,构成了多个团(完全图)

我们就想着把每个团里的所有球都染上同一种颜色,则相同颜色的球不能紧贴在一起。

则我们现在将问题转换为:给你n个染了色的球,相同的球不能放一起,求排列数。

考虑将这些球按照颜色排序,这样便有了一个合理的(可以抽象出状态的)DP顺序。

我们设f[i][j][k]表示:

当前DP到第i位,

有两个球放在一起,它们的颜色相同,并且颜色与第i位的球不同,这样的对共有j个,

有两个球放在一起,它们的颜色相同,并且颜色与第i位的球相同,这样的对共有k个,

的方案数。

因为我们已经排过序,因此颜色相同的球必定紧贴。

1.第 i 位的球和第 i1 位的球颜色不同。

则DP状态的第三维(即k)必为0,因为不存在在它之前并且和它颜色相同的球。我们只需要枚举第二维j

1.1.我们将这个球放在两个颜色不同的球之间。

我们枚举一个k,表示上一个球所代表的颜色中颜色相同且紧贴的对共有k个(k[0,j])。

则有f[i][j][0]+=f[i-1][j-k'][k']*(i-j),因为共有j-k'个相邻且相同且和上一个球的颜色不同的位置,并且共有i-j个可以放球的位置。

1.2.我们将这个球放在两个颜色相同的球之间。

我们仍然枚举一个k,意义相同。这时,k[0,j+1]

则有f[i][j][0]+=f[i-1][j-k'+1][k']*(j+1)。因为放入这个球后就拆开了一对球,因此原来共有 j+1 对这样的球。

2.第 i 位的球和第 i1 位的球颜色相同。

我们需要枚举剩余两维j,k。并且,设在第i位之前,有cnt个和第i位相同的位置。

2.1.我们将这个球放在某个和这个球颜色相同的球旁边。

则共有2cnt(k1)个这样的位置。

因此有f[i][j][k]+=f[i-1][j][k-1]*(2*cnt-(k-1))

2.2.我们将这个球放在两个颜色相同的球之间。

同1.2,有f[i][j][k]+=f[i-1][j+1][k]*(j+1)

2.3.我们将这个球放在两个颜色不同且与这个球颜色不同的球之间。

这次操作没有添加或删除任何对,并且共有i(2cntk)j个位置。

因此有f[i][j][k]+=f[i-1][j][k]*(i-(cnt*2-k)-j)

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
typedef long long ll;
int n,num[310],dsu[310],f[2][310][310];
vector<int>v;
int find(int x){
	return dsu[x]==x?x:dsu[x]=find(dsu[x]);
}
void merge(int x,int y){
	x=find(x),y=find(y);
	if(x==y)return;
	dsu[x]=y;
}
bool che(ll ip){
	ll tmp=sqrt(ip)+0.5;
	return tmp*tmp==ip;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&num[i]),dsu[i]=i;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(che(1ll*num[i]*num[j]))merge(i,j);
	for(int i=1;i<=n;i++)dsu[i]=find(i);
	sort(dsu+1,dsu+n+1);
	f[0][0][0]=1;
	for(int i=1,cnt;i<=n;i++){
		memset(f[i&1],0,sizeof(f[i&1]));
		if(dsu[i]!=dsu[i-1]){
			cnt=0;
			for(int j=0;j<i;j++){
				for(int k=0;k<=j;k++)f[i&1][j][0]=(1ll*f[!(i&1)][j-k][k]*(i-j)+f[i&1][j][0])%mod;//if we put it between two balls of different colours
				for(int k=0;k<=j+1;k++)f[i&1][j][0]=(1ll*f[!(i&1)][j-k+1][k]*(j+1)+f[i&1][j][0])%mod;//if we put it between two balls of the same colours
			}
		}else{
			for(int j=0;j<i;j++){
				for(int k=1;k<=cnt;k++)f[i&1][j][k]=(1ll*f[!(i&1)][j][k-1]*(cnt*2-(k-1))+f[i&1][j][k])%mod;//if we put it next to a ball of the same colour
				for(int k=0;k<=cnt;k++)f[i&1][j][k]=(1ll*f[!(i&1)][j+1][k]*(j+1)+f[i&1][j][k])%mod;//if we put it between two balls of the same colours
				for(int k=0;k<=cnt;k++)f[i&1][j][k]=(1ll*f[!(i&1)][j][k]*(i-(cnt*2-k)-j)+f[i&1][j][k])%mod;//if we put it between two balls of different colours
			}
		}
		cnt++;
	}
	printf("%d\n",f[n&1][0][0]);
	return 0;
}

posted @   Troverld  阅读(169)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示