BZOJ 3197: [Sdoi2013]刺客信条 树hash 费用流 树形Dp

title

BZOJ 3197

LUOGU 3296

简化题意:

给定一棵树和两组权值(权值只有 \(0~1\)),求第一组权值最少改变多少个之后这棵树经过重标号之后与第二组权值相同(即树同构)。

analysis

比较神奇的一道题,当我在看到简化题意之前,我是没有准确的理解题意的,说的有些隐晦呵。

不过从中抽出了这之中的真正题意后,一个算法就已经应运而生:树 \(hash\) 判树同构(不会请右转: [BJOI2015]树的同构 。)

然后这是个树形结构,那就要么 \(dfs\) ,要么树形 \(Dp\)

因为这道题有明确的的可以设置的状态(其他的两个条件都满足哈),所以选择树形 \(Dp\)

状态:设 \(f[i][j]\) 表示 \(树_1\)\(i\) 子树匹配 \(树_2\)\(j\) 子树的最小代价。

状态转移是需要思考的,因为存在很多条件:

  1. 必要条件是 \(i\)\(j\) 所在子树同构而且二者深度相同;
  2. 同时还必须保证这两子树对应所有子节点的最小花费都已经计算出来。

然后就是要树 \(hash\) 判树同构, \(hash\) 值相同的节点,而且深度相同,就是可以对应的。

但是因为是无根树,直接任选根 \(hash\) 可能会有问题,因此还要找出重心作根来 \(hash\) (如果有多个就加一个虚点)。

接下来要处理的问题就是最小花费的匹配,这个东西其实就是一个费用流的模型(我就是照着费用流的标签来写的,谁知道这题这题这么神)。

对于可以对应的两个点 \((i,j)\) ,我们就在这两个点之间连一条费用为 \(f[i][j]\) ,流量为 \(1\) 的边即可。

因为树同构,最终的答案就是 \(f[rt][rt]\)

有一点注意的就是:因为要对每个点都进行一次同构对应建图,所以建出一张新图后,要在跑费用流之前清空之前的最小费用。另外,会 \(ZKW\) 费用流的,就别用 \(EK\) 了。

code

#include<bits/stdc++.h>

typedef long long ll;
const int maxn=1410,maxm=4e5+10,inf=0x3f3f3f3f,p=233;

namespace IO
{
    char buf[1<<15],*fs,*ft;
    inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
    template<typename T>inline void read(T &x)
    {
        x=0;
        T f=1, ch=getchar();
        while (!isdigit(ch) && ch^'-') ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }

    char Out[1<<24],*fe=Out;
    inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
    template<typename T>inline void write(T x,char str)
    {
        if (!x) *fe++=48;
        if (x<0) *fe++='-', x=-x;
        T num=0, ch[20];
        while (x) ch[++num]=x%10+48, x/=10;
        while (num) *fe++=ch[num--];
        *fe++=str;
    }
}

using IO::read;
using IO::write;

template<typename T>inline bool chkMin(T &a,const T &b) { return a>b ? (a=b, true) : false; }
template<typename T>inline bool chkMax(T &a,const T &b) { return a<b ? (a=b, true) : false; }
template<typename T>inline T min(T a,T b) { return a<b ? a : b; }
template<typename T>inline T max(T a,T b) { return a>b ? a : b; }

struct Graph
{
    int ver[maxm<<1],edge[maxm<<1],Next[maxm<<1],cost[maxm<<1],head[maxn],len;
    inline void Clear()
    {
        memset(head,0,sizeof(head));
        len=1;
    }

    inline void add(int x,int y,int z,int c)
    {
        ver[++len]=y,edge[len]=z,cost[len]=c,Next[len]=head[x],head[x]=len;
        ver[++len]=x,edge[len]=0,cost[len]=-c,Next[len]=head[y],head[y]=len;
    }
} G, A;

namespace ZKW
{
    int s,t;
    int dist[maxn];
    bool vis[maxn];
    inline bool spfa()
    {
        memset(dist,0x3f,sizeof(dist));
        memset(vis,0,sizeof(vis));
        std::queue<int>q;q.push(s);
        dist[s]=0, vis[s]=1;
        while (!q.empty())
        {
            int x=q.front();
            q.pop();
            vis[x]=0;
            for (int i=A.head[x]; i; i=A.Next[i])
            {
                if (!A.edge[i]) continue;
                int y=A.ver[i];
                if (dist[y]>dist[x]+A.cost[i])
                {
                    dist[y]=dist[x]+A.cost[i];
                    if (!vis[y]) q.push(y),vis[y]=1;
                }
            }
        }
        if (dist[t]==inf) return false;
        else return true;
    }

    int ans;
    inline int get(int x,int low)
    {
        vis[x]=1;
        if (x==t) return low;
        int tmp=low;
        for (int i=A.head[x]; i; i=A.Next[i])
        {
            int y=A.ver[i];
            if (A.edge[i] && dist[y]==dist[x]+A.cost[i] && (!vis[y] || y==t))
            {
                int a=get(y,min(tmp,A.edge[i]));
                if (a>0)
                {
                    ans+=a*A.cost[i];
                    A.edge[i]-=a;
                    A.edge[i^1]+=a;
                    if (!(tmp-=a)) break;
                }
            }
        }
        return low-tmp;
    }

    inline void NetFlow()
    {
        while (spfa())
        {
            vis[t]=1;
            while (vis[t])
            {
                memset(vis,0,sizeof(vis));
                get(s,inf);
            }
        }
    }
}

using ZKW::NetFlow;
using ZKW::s;
using ZKW::t;
using ZKW::ans;

int siz[maxn],son[maxn],rt[5];
int root,rtsiz,n;
inline void getroot(int x,int fa)
{
    siz[x]=1;
    for (int i=G.head[x]; i; i=G.Next[i])
    {
        int y=G.ver[i];
        if (y==fa) continue;
        getroot(y,x);
        siz[x]+=siz[y];
        chkMax(son[x],siz[y]);
    }
    chkMax(son[x],n-siz[x]);
    if (son[root]==son[x]) rt[++rtsiz]=x;
    else if (son[root]>son[x]) root=rt[rtsiz=1]=x;
}

int can;
ll hs[maxn],r[maxn];
inline void gethash(int x,int fa)
{
    ll sum=0; siz[x]=1;
    for (int i=G.head[x]; i; i=G.Next[i])
    {
        int y=G.ver[i];
        if (y==fa || can==i || can==(i^1)) continue;
        gethash(y,x);
        siz[x]+=siz[y];
        sum+=hs[y];
    }
    hs[x]=(1ll*sum*p)^r[siz[x]];
}

int a[maxn],b[maxn],f[maxn][maxn];
inline void dp(int x,int fa)
{
    for (int i=G.head[x]; i; i=G.Next[i])
    {
        int y=G.ver[i];
        if (y==fa || can==i || can==(i^1)) continue;
        dp(y,x);
    }
    for (int u=1; u<=n; ++u)
    {
        if (hs[u]^hs[x]) continue;
        A.Clear();
        for (int i=G.head[u]; i; i=G.Next[i])
        {
            int y=G.ver[i];
            A.add(y+n,t,1,0);
        }
        for (int i=G.head[x]; i; i=G.Next[i])
        {
            int y=G.ver[i];
            if (y==fa || can==i || can==(i^1)) continue;
            A.add(s,y,1,0);
            for (int k=G.head[u]; k; k=G.Next[k])
            {
                int v=G.ver[k];
                if (hs[y]==hs[v]) A.add(y,v+n,1,f[y][v]);
            }
        }
        ans=0;//记得要清空
        NetFlow();
        f[x][u]=ans+(a[x]!=b[u]);
    }
}

int main()
{
    srand(19260817);
    read(n); s=0, t=(n<<1)+2;
    G.Clear();
    A.Clear();
    for (int i=1,x,y; i<n; ++i) read(x),read(y),G.add(x,y,0,0);
    for (int i=1; i<=n; ++i) read(a[i]);
    for (int i=1; i<=n; ++i) read(b[i]);
    son[0]=inf;
    getroot(1,0);
    if (rtsiz==2)
    {
        for (int i=G.head[rt[1]]; i; i=G.Next[i])
        {
            int y=G.ver[i];
            if (y==rt[2]) { can=i; break; }
        }
        root=++n;
        a[n]=b[n]=0;
        G.add(n,rt[1],0,0);
        G.add(n,rt[2],0,0);
    }
    for (int i=1; i<=n; ++i) r[i]=((rand()%32766)<<15ll)+(rand()%32766);
    memset(f,0x3f,sizeof(f));
    gethash(root,0);
    dp(root,0);
    write(f[root][root],'\n');
    IO::flush();
    return 0;
}

转载于:https://www.cnblogs.com/G-hsm/p/11475504.html

  • 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、付费专栏及课程。

余额充值