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;
}

转载于:https://www.cnblogs.com/ForwardFuture/p/11483941.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值