zoukankan      html  css  js  c++  java
  • 【BZOJ4928】第二题 树hash+倍增

    【BZOJ4928】第二题

    Description

    对于一棵有根树,定义一个点u的k-子树为u的子树中距离u不超过k的部分。
    注意,假如u的子树中不存在距离u为k的点,则u的k-子树是不存在的。
    定义两棵子树是相同的,当且仅当不考虑点的标号时,他们的形态是相同的(儿子的顺序也需要考虑)。给定一棵n个点,点的标号在[1,n],以1为根的有根树。问最大的k,使得存在两个点u !=v,满足u的k-子树与v的k-子树相同。

    Input

    第一行输入一个正整数n。
    接下来读入n个部分,第i个部分描述点i的儿子,且以顺序给出。
    每个部分首先读入一个整数x,代表儿子个数。
    接下来x个整数,代表从左到右儿子的标号
    n ≤ 100000,保证给出的树是合法的

    Output

    输出一个整数k,代表最大的合法的k

    Sample Input

    8
    1
    2
    2
    3 4
    0
    1
    5
    2
    6 7
    0
    1
    8
    0

    Sample Output

    3

    题解:依旧是集训原题。

    由于答案是可二分的,考虑倍增。我们用f[i][x]表示x的-(1<<i)子树的hash值。那么,如果我们已经知道了所有点的-a子树的hash值和-b子树的hash值,如何求出-(a+b)的hash值呢?这就需要我们有一种hash的方法,即保证hash的正确性也要便于我们合并。

    然后看标程看到好多种hash的方法,这里只说一种比较简单的。

    特别地,我们令f[0][x]的hash值为x的儿子数量(你可以想象,如果一棵树的DFS序确定了,DFS序上每个点的权值就是它的儿子数量,那么这个DFS序就能唯一的表示一棵树)。然后在合并-a和-b的时候如何处理呢?我们按照DFS序遍历x的所有到x的距离为a的子孙,然后设它们的-b子树的hash值分别为b1,b2...bm,那么我们相当于得到一个序列{a,b1,b2...bm},我们将这个序列hash一下(hash=a*basem+b1*basem-1+...bm*1)。最后,我们将所有点的hash值排个序,反离散化一下,得到的排名就是x的-(a+b)子树的hash值。

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    typedef unsigned long long ull;
    const int maxn=100010;
    int n,mx,ans,len,now;
    const ull bas=2543323;
    ull hs[20][maxn],f[maxn],nf[maxn],pv[maxn];
    vector <int> ch[maxn];
    int dep[maxn],p[maxn],st[maxn],md[maxn],vis[maxn];
    int rd()
    {
    	int ret=0,f=1;	char gc=getchar();
    	while(gc<'0'||gc>'9')	{if(gc=='-')f=-f;	gc=getchar();}
    	while(gc>='0'&&gc<='9')	ret=ret*10+gc-'0',gc=getchar();
    	return ret*f;
    }
    void dfs(int x,ull *a,ull *b,ull *c)
    {
    	int i,y;
    	if(x==1)	p[0]=st[0]=0,dep[1]=1;
    	st[dep[x]]=x,md[x]=dep[x],a[x]=b[x];
    	if(dep[x]>len)	(a[st[dep[x]-len]]*=bas)+=c[x];
    	for(i=0;i<ch[x].size();i++)	y=ch[x][i],dep[y]=dep[x]+1,dfs(y,a,b,c),md[x]=max(md[x],md[y]);
    	st[0]--;
    }
    bool cmp(int a,int b)
    {
    	return pv[a]<pv[b];
    }
    void merge(ull *a,ull *b,ull *c,int l)
    {
    	len=l;
    	dfs(1,a,b,c);
    	int i,j;
    	for(i=1;i<=n;i++)	p[i]=i,pv[i]=a[i];
    	sort(p+1,p+n+1,cmp);
    	for(i=1,j=0;i<=n;i++)
    	{
    		if(pv[p[i]]>pv[p[i-1]])	j++;
    		a[p[i]]=j;
    	}
    }
    int check(ull *a,ull *b,ull *c,int l)
    {
    	merge(a,b,c,ans);
    	now++;
    	for(int i=1;i<=n;i++)
    	{
    		if(md[i]-dep[i]>=l)
    		{
    			if(vis[a[i]]==now)	return 1;
    			vis[a[i]]=now;
    		}
    	}
    	return 0;
    }
    int main()
    {
    	n=rd();
    	int i,j,a;
    	for(i=1;i<=n;i++)
    	{
    		a=rd(),hs[0][i]=a;
    		for(j=1;j<=a;j++)	ch[i].push_back(rd());
    	}
    	for(i=1;i<=16;i++)	merge(hs[i],hs[i-1],hs[i-1],1<<i-1);
    	for(i=16;i>=0;i--)
    	{
    		if(check(nf,f,hs[i],ans+(1<<i)))
    		{
    			ans+=(1<<i);
    			memcpy(f,nf,sizeof(nf));
    		}
    	}
    	printf("%d",ans);
    	return 0;
    }

     

  • 相关阅读:
    PHP实现微信小程序人脸识别刷脸登录功能
    thinkphp3.2.3中设置路由,优化url
    ThinkPHP URL 路由简介
    在 Linux 下搭建 Git 服务器
    图解 Android 广播机制 狼人:
    手机系统竞争背后的利益竞逐 狼人:
    你必须知道的Windows Phone 7开发 狼人:
    展望Android之前世今生 狼人:
    在美做开发多年,写给国内iPhone开发新手 狼人:
    NDK入门项目实战 狼人:
  • 原文地址:https://www.cnblogs.com/CQzhangyu/p/7088123.html
Copyright © 2011-2022 走看看