• [AHOI2018初中组]球球的排列


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

    论DP的百种用法之一

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

    首先分析性质。我们发现,这种不能放在一起的关系具有传递性。因为如果\(xy=a^2,xz=b^2\),那么\(yz=\dfrac{(xy)(yz)}{x^2}=\dfrac{a^2b^2}{x^2}=\Big(\dfrac{ab}{x}\Big)^2\)

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

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

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

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

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

    当前DP到第\(i\)位,

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

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

    的方案数。

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

    1.第 \(i\) 位的球和第 \(i-1\) 位的球颜色不同。

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

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

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

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

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

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

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

    2.第 \(i\) 位的球和第 \(i-1\) 位的球颜色相同。

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

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

    则共有\(2*cnt-(k-1)\)个这样的位置。

    因此有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-(2*cnt-k)-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;
    }
    

  • 相关阅读:
    【非技术】谈谈业务6W+H
    WinForm二三事(三)Control.Invoke&Control.BeginInvoke
    企业应用架构模式读书笔记(一)
    WinForm二三事(四)界面布局(上)
    WinForm二三事(二)异步操作
    WinForm二三事(一)消息循环
    白话基础之虚拟存储器
    不清楚自己的位置,会走很多弯路
    WinForm二三事(一)补遗
    技术、业务、市场
  • 原文地址:https://www.cnblogs.com/Troverld/p/14596825.html
Copyright © 2020-2023  润新知