树的Hash方法?

写这篇博文的主要还是因为自己菜得抠脚…………

弱校联盟的十一专场的第三天是JAG Practice Contest for ACM-ICPC Asia Regional 2016,其中的E题大意是给一颗有根树,问有多少对子树每个深度的节点数都相同。从长春回来之后自己一直不是很在状态,错把题意当成了问有多少对子树完全同构,然后懵逼三个小时,事后去网上查找树同构的资料,回想起这题问的不是树同构,而是每个深度的节点数是否相同,尴尬死了。真的要好好反省一下。

单单思考这道题,看了网上别人的题解,大致思路很简单,就是用一个素数p给每个深度一个权值,根节点的权值为1,其每个子节点的权值为p,其子节点的子节点的权值就为p^2,然后一个树的hash值就是其所有子节点的权值之和。 可以确定的是:如果两棵树各个深度的节点个数都一样,毫无疑问这两棵树的hash值是一样的。而素数只要选择稍稍得当,就很难会出现两棵不相同的树的hash值相同。(其实这里不能主观臆想,应该要有一个严格的证明,但是我数学基础比较弱,而且没有相关的知识储备,暂且把前面这句话当做结论记一下,希望不会有错,自己以后知道了相关原理再回来补充)于是此题就可以通过dfs的方式求各个子树的值,用map标记后就可以求得解了。

题目链接: https://acm.bnu.edu.cn/v3/contest_show.php?cid=8504#problem/E
相关代码供参考:
为了防止hash值过大,用了unsigned long long进行了取模

#include
#include
#include
#include
#include
using namespace std;
#define MS(x,y) memset(x,y,sizeof(x))
typedef unsigned long long ULL;
typedef long long LL;
const int MAXN=1e5+5;
const int P=1e5+3;
struct Edge{
    int nxt,v;
}edge[MAXN];
int head[MAXN],edgenum;
void addedge(int u,int v){
    edge[edgenum].v=v;
    edge[edgenum].nxt=head[u];
    head[u]=edgenum++;
}
map M;
map::iterator it;
int n;

ULL dfs(int u){
    ULL ret=1;
    for(int i=head[u];~i;i=edge[i].nxt){
        ret+=P*dfs(edge[i].v);
    }
    M[ret]++;
    return ret;
}

int main(){
    while(~scanf("%d",&n)){
        MS(head,-1);
        edgenum=0;
        M.clear();
        int u,v;
        for(int i=1;i
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        dfs(1);
        LL ans=0;
        for(it=M.begin();it!=M.end();++it){
            ans+=it->second*(it->second-1)/2;
        }
        printf("%lld\n",ans);
    }
}

然后如果这题问的是有几颗子树完全相同呢?这个问题又要通过什么方法来解决呢?
之前问题的解决方法显然行不通,07年国家集训队论文-杨弋《Hash在信息学竞赛中的一类应用》中,就有一个很简单的反例:
树的Hash方法?_第1张图片
按照之前说的方法,很容易发现两棵不同构的树的hash值是相同的(明明是显而易见啊…………)
麻烦读者老爷自己去计算一下两棵树的值。
解决方法是什么呢?其实很简单,就是把每个树的值当做做一个平方,也就是说,一个数的hash值就是其所有子节点的平方和。请读者老爷再计算一下这两棵树的值,明显不同了。
再确定一件事: 如果两棵树是同构的,毫无疑问这两棵树的hash值是一样的。而素数只要选择稍稍得当,就很难会出现两棵不同构的树的hash值相同。
poj1635就是这样一道题: http://poj.org/problem?id=1635
相关代码供参考:

#include
#include
#include
using namespace std;
typedef long long LL;
const int MOD=1e9+7;
const int P=1e5+3;
char str1[3100],str2[3100];
char *str;

LL dfs(int dep){
    LL sum=1;
    while(*str&&*str++=='0')
        sum=(sum+P*dfs(dep+1))%MOD;
    return (sum*sum)%MOD;
}

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%s%s", str1, str2);
        str=str1;
        LL a=dfs(1);
        str=str2;
        LL b=dfs(1);
        if(a==b) puts("same");
        else puts("different");
    }
}

国家队论文里还提到了一个类似的方法,每次随机生成一些数,作为每个节点的权值,然后和上面方法返回权值的平方。可以看看这篇博客: http://www.cnblogs.com/jackiesteed/articles/2065307.html(其实我poj1635的代码就是看了这篇博客后码的……)

你可能感兴趣的:(杂,模板)