您的位置:首页 > 其它

bzoj 4539 hnoi2016 树 倍增lca+主席树

2018-02-09 16:55 323 查看
题目:bzoj 4539 hnoi2016 树

为了做这题专门温习了一天的主席树,结果发现主席树在里边并不是主要作用?

主体部分基本一遍写过,不过这题很坑的是有几个地方需要开longlong,因为1e5*1e5能够爆int

读数的时候也得读longlong

思路:一开始我以为是递归似的合并,然后很差异这样树的大小是指数级的,后来才发现一直以一个模板树来复制。。。

这样就很好做了,将每个复制下来的树当做一个节点,大节点之间的边权是每个复制下的树的根节点的距离,而且也不用真的连边,记到倍增数组里就好;很显然这样大节点中到根的距离就是每个小树的根到大树根的距离,然后再加上要求的点到小根的距离就是要求的点到大树根的距离;

这样两个要求的点到根的距离减去二倍lca到根的距离就是所求的值;

因为输入的点很大,也不可能真建这么多点,所以可以每个大节点记录他的根,子树大小,还有之前的大节点共占用了多少节点,这样二分找到大节点,再主席树找到对应模板树的节点就可以了。

细节见代码,不到200行的码子是我这几天写的最恶心的一题。。。虽然主体过的挺顺利,但那个开longlong的坑还是栽了好久。。。趁着脑子还热乎,赶紧给代码多打几行注释。。。

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdio>
#define LL long long
#define random(a,b) (a+rand()%(b-a+1))
const int maxn=100005;
int n,m,q;
struct asd
{
int next,to,val;
}edge[maxn*4];//其实开2倍应该就行
int etot=0;
int dp[maxn*2][20];
LL dpl[maxn*2][20];
int node[maxn];
LL d[maxn*2];
void add(int x,int y,int z)
{
edge[++etot].next=node[x];
edge[etot].to=y;
edge[etot].val=z;
node[x]=etot;
}
int tmp1,tmp2;
int root=n+1;
int start[maxn],end[maxn];
int rt[maxn];
struct zxs
{
int l,r,sum;
}t[maxn*40];
int ntot=0;
int ttot=0;
void build(int l,int r,int &x,int y,int pos)
{
t[++ttot]=t[y],x=ttot,t[x].sum+=1;//一开始我竟然sb地忘写了个等号。。。
if(l==r)return;
int mid=(l+r)>>1;
if(pos<=mid)build(l,mid,t[x].l,t[y].l,pos);
else build(mid+1,r,t[x].r,t[y].r,pos);
}
void dfs(int x,int fa)
{
d[x]=d[fa]+1;
dp[x][0]=fa;//把小树上的父节点赋给倍增数组
start[x]=++ntot;
build(1,n,rt[ntot],rt[ntot-1],x);
for(int i=node[x];i;i=edge[i].next)
if(edge[i].to!=fa)
dfs(edge[i].to,x);
end[x]=ntot;
}
struct qqq
{
LL l,r;//l到r是这个大节点包含的小节点编号
int rt,nlc,dlc;//rt是这个复制下来的树的根,nlc是大节点编号,dlc是和上一个大节点相接的小节点的编号;
qqq()
{
l=r=rt=nlc=dlc=0;
}
qqq(LL x,LL y,int z,int z2,int z3)
{
l=x,r=y,rt=z,nlc=z2,dlc=z3;
}
}qu[maxn];//包含每个大节点的信息
int qtot=0;
struct ppp
{
int id,lc;//id是其对应的qu ,lc是其在模板树中的编号
ppp(int x,int y)
{
id=x,lc=y;
}
};
int query(int l,int r,int x,int y,int pos)
{
while(l<r)
{
int mid=(l+r)>>1;
int tsum=t[t[y].l].sum-t[t[x].l].sum;
if(tsum>=pos)r=mid,x=t[x].l,y=t[y].l;
else l=mid+1,x=t[x].r,y=t[y].r,pos-=tsum;//主席树
}
return l;
}
ppp getpos(LL x)
{
int l=1,r=qtot;
while(l<r)
{
int mid=(l+r)>>1;
if(qu[mid].r<x)l=mid+1;
else r=mid;//二分x所在的qu
}
return ppp(l,query(1,n,rt[start[qu[l].rt]-1],rt[end[qu[l].rt]],x-qu[l].l+1));
}
LL getdis(int x,int y)
{
return d[x]-d[y]<0 ? d[y]-d[x] : d[x]-d[y];//计算小节点间的距离
}
void insert(LL x,LL y)
{
ppp tlc=getpos(y);
LL size=end[x]-start[x]+1;//新复制的子树的大小
qtot++;
qu[qtot]=qqq(qu[qtot-1].r+1,qu[qtot-1].r+size,x,qtot+n,tlc.lc);//需要另加父亲指针
dp[qtot+n][0]=qu[tlc.id].nlc;
dpl[qtot+n][0]=getdis(tlc.lc,qu[tlc.id].rt)+1;//求大节点到父亲大节点的距离
d[qtot+n]=d[qu[tlc.id].nlc]+1;//大节点的深度,用于求lca
for(int i=1;i<=18;i++)
{
dp[qtot+n][i]=dp[dp[qtot+n][i-1]][i-1];
dpl[qtot+n][i]=dpl[dp[qtot+n][i-1]][i-1]+dpl[qtot+n][i-1];//倍增
}
}
void swp(ppp &x,ppp &y)
{
ppp z=y;
y=x;
x=z;
}
int LCA(int x,int y)
{
if(d[x]<d[y])std::swap(x,y);
for(int i=18;i>=0;i--)
if(d[dp[x][i]]>=d[y])x=dp[x][i];
if(x==y)return x;
for(int i=18;i>=0;i--)
if(dp[x][i]!=dp[y][i])x=dp[x][i],y=dp[y][i];
return dp[x][0];//求模板树中两点的lca
}
LL findlca(ppp x,ppp y)//求总体lca到根的距离
{
int tx=qu[x.id].nlc,ty=qu[y.id].nlc;
if(tx==ty)return d[LCA(x.lc,y.lc)]-d[qu[y.id].rt]+dpl[ty][18];//如果所求的两点一开始就在同一大节点,就直接返回大节点到根的距离加上两点lca到这个大节点的小根的距离
if(d[tx]<d[ty])swp(x,y),std::swap(tx,ty);
for(int i=18;i>=0;i--)
{
if(d[dp[tx][i]]>d[ty])tx=dp[tx][i];//倍增,需注意深度同步应相差1,不然不知道是从哪个小节点进入的
}
if(dp[tx][0]==ty)return d[LCA(qu[tx-n].dlc,y.lc)]-d[qu[y.id].rt]+dpl[ty][18];//如果此时互为大节点中的父子,返回进入点到父节点小根的距离加上父节点到大根的距离
if(d[tx]>d[ty])tx=dp[tx][0];//深度同步
for(int i=18;i>=0;i--)
if(dp[tx][i]!=dp[ty][i])tx=dp[tx][i],ty=dp[ty][i];
int tz=dp[ty][0];
return d[LCA(qu[tx-n].dlc,qu[ty-n].dlc)]-d[qu[tz-n].rt]+dpl[tz][18];//最理想化的情况,返回两个进入点的lca到当前lca大节点的小根的距离加上大节点到大根的距离
}
LL fix(ppp tlc)//计算大节点到大根的距离
{
return getdis(tlc.lc,qu[tlc.id].rt)+dpl[qu[tlc.id].nlc][18];
}
LL findans(LL x,LL y)
{
ppp tlc1=getpos(x);
ppp tlc2=getpos(y);
LL lca=findlca(tlc1,tlc2);
return fix(tlc1)+fix(tlc2)-lca*2;//减去二倍的lca到大根的距离
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<n;i++)
{
scanf("%d%d",&tmp1,&tmp2);
add(tmp1,tmp2,1);
add(tmp2,tmp1,1);
}
qu[++qtot]=qqq(1ll,(LL)n,1,n+1,0);
d[n+1]=0;
d[0]=0;
dfs(1,0);
for(int k=1;k<=18;k++)
for(int i=1;i<=n;i++)
dp[i][k]=dp[dp[i][k-1]][k-1];//倍增模板树中的节点
LL tp1,tp2;
for(int i=1;i<=m;i++)
{
scanf("%lld%lld",&tp1,&tp2);//不要忘了longlong
insert(tp1,tp2);
}
for(int i=1;i<=q;i++)
{
scanf("%lld%lld",&tp1,&tp2);
printf("%lld\n",findans(tp1,tp2));
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: