【BZOJ3899】仙人掌树的同构-圆方树+树上哈希+DP

测试地址:仙人掌树的同构
题目大意:定义一棵仙人掌树为,每个点最多在一个环中的无向图,且图中的环都是简单环。问有多少种点的置换,使得置换后的图和原图相同。 n1000 n ≤ 1000
做法:本题需要用到圆方树+树上哈希+DP。
首先显然的是,仙人掌同构就等同于圆方树同构。不过这题的仙人掌定义和一般的仙人掌有些不同:是每个点最多在一个环中,而不是每条边。又因为没有重边,所以没有大小为 2 2 的环。通过这个性质我们可以更简单地写出圆方树。为了讨论方便,这里把度数为2的方点都省略,直接将它连接的两个点连接。
我们随便选一个圆点作为根,令 f(v) f ( v ) 为以点 v v 为根的子树中,当点v确认置换成某一个等价的点时,这棵子树内部有多少种置换。那么:
对于一个圆点,考虑它所有子树的哈希值,如果有 k k 个子树的哈希值相同,那它们的点之间可以互换,因此方案数乘上k!
而对于一个方点,因为环实际上是有顺序的,所以不能像上面一样随便互换。因为环的某一个点已经确定了(该方点的父亲),所以我们只能对这个环做翻转变换,看它和原图是不是同构,如果是,答案就乘上 2 2 <script type="math/tex" id="MathJax-Element-1738">2</script>。
在哈希时也要注意,要把圆点和方点区分,要给它们设置不同的哈希参数。而对于方点的哈希,因为我们说过了,环上的点是有顺序的,不能打乱,所以我们把方点的子树正反哈希两遍,取其中的最小值作为方点的哈希值即可。注意写的时候一定要非常注意环上点的顺序。
那这样是不是就完了呢?还没有。注意到我们上面求出的是,选定的根确定的情况下置换的数目,所以我们需要对每个圆点往下都做一遍树哈希,来看有多少个点和选定的根等价,最后再乘上这个点数才是答案。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const ll mod=1000000003;
const ll P[2]={131,103};
int n,m,first[2010]={0},tfirst[2010]={0},tot=0;
int fa[2010],totpbc,belong[2010];
ll fac[2010],tmp[2010],siz[2010],down[2010],Hash[2010],ans=1;
bool vis[2010]={0};
struct edge
{
    int v,next;
}e[5010],t[5010];

void insert(edge *e,int *first,int a,int b)
{
    e[++tot].v=b;
    e[tot].next=first[a];
    first[a]=tot;
}

void build(int v)
{
    vis[v]=1;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].v!=fa[v])
        {
            if (vis[e[i].v])
            {
                if (belong[e[i].v]) continue;
                belong[e[i].v]=++totpbc;
                insert(t,tfirst,e[i].v,totpbc);
                insert(t,tfirst,totpbc,e[i].v);
                for(int p=v;p!=e[i].v;p=fa[p])
                {
                    belong[p]=totpbc;
                    insert(t,tfirst,p,totpbc);
                    insert(t,tfirst,totpbc,p);
                }
            }
            else fa[e[i].v]=v,build(e[i].v);
        }
    if (!belong[v]) belong[v]=++totpbc;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].v!=fa[v]&&belong[v]!=belong[e[i].v])
        {
            insert(t,tfirst,v,e[i].v);
            insert(t,tfirst,e[i].v,v);
        }
}

void dfs(int v,int fa)
{
    int type=(v>n),to=0;

    siz[v]=1;
    for(int i=tfirst[v];i;i=t[i].next)
    {
        if (t[i].v!=fa)
        {
            dfs(t[i].v,v);
            siz[v]+=siz[t[i].v];
        }
        else to=i;
    }
    tot=0;
    if (to)
    {
        for(int i=t[to].next;i;i=t[i].next)
            tmp[++tot]=down[t[i].v];
    }
    for(int i=tfirst[v];i!=to;i=t[i].next)
        tmp[++tot]=down[t[i].v];

    if (v<=n)
    {
        for(int i=1;i<=tot;i++)
            vis[i]=0;
        for(int i=1;i<=tot;i++)
            if (!vis[i])
            {
                int cnt=0;
                for(int j=i;j<=tot;j++)
                    if (tmp[i]==tmp[j])
                    {
                        vis[j]=1;
                        cnt++;
                    }
                ans=ans*fac[cnt]%mod;
            }
        sort(tmp+1,tmp+tot+1);
        down[v]=0;
        for(int i=1;i<=tot;i++)
            down[v]=down[v]*P[type]+tmp[i];
        down[v]=down[v]*P[type]+siz[v];
        down[v]*=siz[v];
    }
    else
    {
        bool flag=1;
        for(int i=1;i<=tot;i++)
            if (tmp[i]!=tmp[tot-i+1])
            {
                flag=0;
                break;
            }
        if (flag) ans=(ans<<1)%mod;
        down[v]=0;
        for(int i=1;i<=tot;i++)
            down[v]=down[v]*P[type]+tmp[i];
        down[v]=down[v]*P[type]+siz[v];
        down[v]*=siz[v];
        ll now=0;
        for(int i=tot;i>=1;i--)
            now=now*P[type]+tmp[i];
        now=now*P[type]+siz[v];
        now*=siz[v];
        down[v]=min(down[v],now);
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        insert(e,first,a,b);
        insert(e,first,b,a);
    }
    fac[0]=1;
    for(ll i=1;i<=(n<<1);i++)
        fac[i]=fac[i-1]*i%mod;

    tot=0;totpbc=n;
    fa[1]=0;
    build(1);
    dfs(1,0);
    ll same=down[1],cnt=1,now=ans;
    for(int i=2;i<=n;i++)
    {
        dfs(i,0);
        if (same==down[i]) cnt++;
    }
    printf("%llu\n",now*cnt%mod);

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用dp来解决这个问题,具体来说,我们可以将这个图看作是一棵,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值