UVA11540 Sultan's Chandelier

题意与数据范围

给定一定长度的列表(以字符形式给出),你需要用 \(C\) 种颜色对每一对匹配的括号进行染色,两种方案被认为本质相同当且仅当在多次进行对任意一对括号内的元素全部平移一个单位的操作后,两个列表完全相同。求本质不同的染色方案数,答案对 \(10^9+7\) 取模

数据组数 \(T\le 4000\)\(C\le 100\) ,括号数 \(\le 200\)

这么描述题意比较抽象,我们来看个例子:

有这样一个列表 \([\ [\ ],[\ ],[\ ]\ ]\)

我们有两种染色方案:

\(1.\) 将最外面的括号染成 \(a\) 这种颜色,将里面三个括号分别染成 \(c,b,b\) 三种颜色

\(2.\) 将最外面的括号染成 \(a\) 这种颜色,将里面三个括号分别染成 \(b,b,c\) 三种颜色

对于第二种方案,我们将最外层的那对括号中所含的元素整体向左平移一个单位,得到 $ b,c,b$ ,再向左平移一个单位,得到 \(c,b,b\) ,那么内部三个括号染色方案就一样了,而最外层都染成了 \(a\) 色,所以这两种染色方案等价

Solution

首先将这玩意儿转成树形结构,直接用一个栈搞一搞就行了

然后考虑树形DP,设 \(dp_i\) 表示在以 \(i\) 为根的子树中本质不同的染色方案数

显然当 \(i\) 为叶子结点时有 \(dp_i=C\) ,所以接下来我们考虑 \(i\) 不为叶子结点的情况

考虑 \(\text{Burnside}\) 引理,可以发现对于任意一个节点 \(i\) ,它想要拥有置换的前提是它儿子节点的序列必须要存在一个循环节,使得任意一对循环节对应节点的子树都是同构的

判断树同构的话树哈希一下就可以了,而求解最小循环节就用 \(\text{KMP}\) 算法的那个结论就行了

假设有一个节点 \(i\) ,它有 \(n\) 个子节点,最小循环节长度为 \(n/k\) ,共有 \(k\) 个循环节,即它的置换群的阶为 \(k\)

如果将这些置换按“平移 \(0\) 个单位”、“平移 \(n/k\) 个单位”、“平移 \(2\times (n/k)\) 个单位”......这样的次序依次排列下去的话,那么容易发现,对于第 \(j\) 个置换,共有 \(\gcd(k,j-1)\) 种不同的循环节

\(val\) 表示最小循环节内部本质不同的选择方案,\(f_i\) 表示以第 \(i\) 个子节点为根的子树中本质不同的选择方案,那么有

\[val=\prod\limits_{i=1}^{n/k}f_i \]

因为相同类型的循环节选择的方案必须相同,所以共有 \(val\) 种方案。而我们一共有 \(\gcd(k,j-1)\) 种不同的循环节,所以对于第 \(j\) 个置换,有 \(val^{\gcd(k,j-1)}\) 种方案

那么对于上述的节点 \(i​\) ,有

\[dp_i=C\times \frac{1}{k}\times \sum\limits_{i=1}^{k}(\prod\limits_{j=1}^{n/k}f_j)^{\gcd(k,i-1)} \]

然后直接DFS计算一下就可以了,答案是 \(dp_1\)

代码如下:

upd:之前树哈希的姿势不对,不能直接sort哈希,应该将子节点序列倍长,然后对每一个长度为 \(n\) 的区间进行hash,取hash值最小的作为最后该节点 \(i\) 的hash值。代码已更新

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N=5e2+10;
const int bas=1997;
const int mod=1e9+7;
int T,C,dp[N],n,tot,s[N],h[N],fac[N],cnt,st[N],top,inv[N],son[N],c[N],f[N],failed[N],k,now,gcd[N][N];char p[N];
vector<int>g[N];
inline int trans(char c){if(c=='[')return 1;else return 2;}
inline int exgcd(int x,int y){int r;if(y)swap(x,y);while(x&&y)r=x%y,x=y,y=r;return x;}
inline void Preprocess(){
	fac[0]=1;for(register int i=1;i<=N-10;i++)fac[i]=1ll*fac[i-1]*bas%mod;
	inv[0]=inv[1]=1;for(register int i=2;i<=N-10;i++)inv[i]=(-1ll*mod/i*inv[mod%i]%mod+mod)%mod;
	for(register int i=0;i<=N-10;i++)
		for(register int j=0;j<=N-10;j++)
			gcd[i][j]=exgcd(i,j);
}
inline void Add(int &x,int y){x+=y;x-=x>=mod? mod:0;}
inline int MOD(int x){x-=x>=mod? mod:0;return x;}
inline int Minus(int x){x+=x<0? mod:0;return x;}
inline int fas(int x,int p){int res=1;while(p){if(p&1)res=1ll*res*x%mod;p>>=1;x=1ll*x*x%mod;}return res;}
inline void DFS(int u){
	son[u]=1;h[u]=1;dp[u]=0;int all=(int)g[u].size();
	if(!all){h[u]=MOD(1ll*h[u]*bas%mod+2);dp[u]=C;return;}
	for(register int i=0;i<all;i++){int v=g[u][i];DFS(v);son[u]+=son[v];}
	for(register int i=0;i<all;i++){int v=g[u][i];c[i+1]=h[v];f[i+1]=dp[v];}
	for(register int i=0;i<=all;i++)failed[i]=0;
	failed[1]=1;k=1;now=2;
	while(now<=all){
		if(c[now]==c[k]||k==1){
			if(c[now]==c[k])failed[now]=++k;
			else failed[now]=k;now++;
		}
		else k=failed[k-1];
	}
	int w;if(failed[all]>1&&all%(all-failed[all]+1)==0)w=all-failed[all]+1;else w=all;
	int num=all/w,val=1;
	for(register int i=1;i<=w;i++)val=1ll*val*f[i]%mod;
	for(register int i=1;i<=num;i++)Add(dp[u],fas(val,gcd[num][i-1]));
	dp[u]=1ll*dp[u]*inv[num]%mod*C%mod;int sum=0;
	for(register int i=1;i<=all;i++)sum=MOD(1ll*sum*bas%mod+c[i]);
	h[u]=sum;
	for(register int i=1;i<all;i++)sum=MOD(1ll*Minus(sum-1ll*fac[all-1]*c[i]%mod)*bas%mod+c[i]),h[u]=min(h[u],sum);
	Add(h[u],fac[all]);h[u]=MOD(1ll*h[u]*bas%mod+2);
}
int main(){
	scanf("%d",&T);Preprocess();
	for(register int Case=1;Case<=T;Case++){
		scanf("%s",p+1);n=strlen(p+1);tot=0;scanf("%d",&C);
		for(register int i=1;i<=n;i++)
			if(p[i]!=',')s[++tot]=trans(p[i]);n=tot;
		top=cnt=0;for(register int i=1;i<=n;i++)g[i].clear();
		for(register int i=1;i<=n;i++)
			if(s[i]==1)st[++top]=++cnt;
			else if(i!=n)g[st[top-1]].push_back(st[top]),top--;
		DFS(1);printf("Case #%d: %d\n",Case,dp[1]);
	}
	return 0;
}
posted @ 2019-09-07 23:41  ForwardFuture  阅读(193)  评论(0编辑  收藏  举报