acm 2015北京网络赛 F Couple Trees 树链剖分+主席树

时间:2022-02-16 05:20:28

Couple Trees

Time Limit: 1 Sec

Memory Limit: 256 MB

题目连接

http://hihocoder.com/problemset/problem/1232

Description

"Couple Trees" are two trees, a husband tree and a wife tree. They
are named because they look like a couple leaning on each other. They
share a same root, and their branches are intertwined. In China, many
lovers go to the couple trees. Under the trees, lovers wish to be
accompanied by a lifetime.

Ada and her boyfriend Asa came to the couple trees as well. They were
very interested in the trees. They were all ACMers, so after careful
observation, they found out that these two trees could be considered as
two "trees" in graph theory. These two trees shared N vertices which
were labeled 1 to N, and they all had exactly N vertices. Vertices 1 was
the root of both trees.

Ada and Asa wanted to know more about the trees' rough bark, so each
of them put one thumb at a vertices. Then they moved their thumbs
towards the root. Ada moved along the wife tree, and Asa moved along the
husband tree. Of course, they could moved at different speed.

At that moment, a thought suddenly came to Ada's mind: their thumbs
may meet before the root. Which one was the earliest possible meeting
vertex? And how many vertices would Ada and Asa encounter on the way to
the meeting vertex?

Input

The input consists of no more than 8 test cases.

For each test case:

The first line contains two integers, N and M, indicating the number of vertices and the number of queries.(1≤N,M≤100,000)

The next line contains N−1 integers. It describes the structure of
wife tree in this way: If the ith integer is k, it means that the vertex
labeled k is the father vertex of the vertex labeled (i+1) . It's
guaranteed that a vertex X's father vertex can't have a larger label
than X does.

The next line describes the husband tree in the same way.

Then next M lines describe the queries. Each line contains two
integers Xi and Yi. Let Ki be the earliest possible meeting vertex of
the ith query (K0 is defined as 0). In the ith query, Ada's thumb was
put at the vertex labeled (Xi+Ki−1) mod N + 1 and Asa's thumb was put at
the vertex labeled (Yi+Ki−1) mod N + 1.(1≤Xi,Yi≤N) at the beginning.

Output

For each test case:

Output the answer for each query in a single line. The answer
contains three integers: the earliest possible meeting vertex, the
number of the vertices Ada will encounter and the number of the vertices
Asa will encounter (including the starting vertex and the ending
vertex). In particular, if they put their thumb at the same vertex at
first, the earliest possible meeting vertex should be the starting
vertex.

Sample Input

5 1
1 2 3 3
1 1 3 2
4 3
5 3
1 1 2 2
1 2 2 1
5 3
5 4
3 5
5 3
1 1 2 2
1 2 3 1
1 4
1 1
3 4

Sample Output

3 2 2
1 1 3
1 2 1
2 2 1
1 2 2
3 1 1
2 1 2

HINT

题意

给你两棵树,都同时往上爬,问你这两个人都能够经过的点中,最大的点是什么,并且都各走了多少步。

注意父节点的编号一定比子节点小。

题解:

本来是想做做倍增的题,然后不小心搜到了这道。想了好久还是不会,看了题解才明白,原来倍增是在优化暴力,但是竟然比正解快!

1.那我们先来讲讲倍增的具体做法吧。

先想想最暴力的方法:就是每次找编号较大的一个的点往上爬,爬到超过另一个点,再换这个点重复这个操作。不难证明如果他们在中途相遇就是我们要找的答案。

但是一个一个往上爬是不是太慢了,确实,那我们就用倍增加速即可。

2.我们怎么能满足于此呢,正解其实是树链剖分+主席树

我一开始看题解也是很懵逼,看了好多遍,都没看懂,于是直接看代码,终于明白题解的意思了,真是太神奇了。

我先讲讲具体做法,之后再分析怎么一步一步想出来的。

(1)我们把第一棵树剖成链,第二棵树构造主席树。第二颗树每个结点都是一颗一棵线段树,每个结点形成线段树包含这个点到根结点的某些信息,合起来就叫做主席树。

(2)那么主席数需要包含什么信息呢?这个我还真不太好形象地解释。

    不如我先来说说数剖的作用吧:树剖即把一条链变成log段连续的序列(要不断告诉自己是dfs序连续,所以我们要利用dfs序列)。

    所以我们主席树维护的是树1的dfs序列,也就是树2的某个点i,我们令rk[i]位置的值(即i的dfs序)为i。这样树1的每一段x,top[x],只要在主席树里求rk[top[x]]~rk[x]的最大值即可。

(3)这么说如果不会可能还是不会,只用理解一下大概思路就行,代码很容易看懂,配合代码理解感觉更轻松。

最后如果你想研究的透彻一点的话,可以看看接下来的分析。先来把模型简化一下:

问题1.给你两个序列a和b,每次询问给你一个数j,求最小的k1使得a[k1]=b[j+k2](k2>=0)

问题2.询问改成两个数i,j,求最小的k1使得a[i+k1]=b[j+k2]

第一问我们可以把数组a重新编号为{1,2,3,4,...,n},也就是原本的a[x]变成x,然后b数组也是同样变化,即 if (b[y]=a[x])b[y]=x。这样只用在b数组[j,n]区间里求最小值即可。

第二问,那么就不是简单地求最小值了,我们要求大于等于等于i的最小值(注意此时,a数组已经变成{1,2,3,..n}了,b数组也相应的变了)。

时刻说出自己要求的东西也可以帮我们理清思路:

(1)现在我们已经不用管第一个序列了,因为他是一个公差为1的等差数列

(2)我们要求数组二 b[j,n]中的大于i的最小值,求出i在b[j,n]中的排名,也就得到了我们想要的答案。

我都说到这个份上了,应该能想到主席树了。如果没想到请赶快记下主席树的两大板子:

(1)区间第k大

(2)区间k的排名(这个就是我们的题目要用的)

如果你真正理解了上面两个问题,那么两棵数和两个序列其实殊途同归,树链剖分dfs序列其实就是把数组a变成等差数列的过程,二且因为父节点一定小于子节点,所以k1最小等价于k2最小。

代码:

lca优化暴力(依靠数据水)

 #include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 200050
int n,m;
template<typename T>void read(T&x)
{
ll k=; char c=getchar();
x=;
while(!isdigit(c)&&c!=EOF)k^=c=='-',c=getchar();
if (c==EOF)exit();
while(isdigit(c))x=x*+c-'',c=getchar();
x=k?-x:x;
}
void read_char(char &c)
{while(!isalpha(c=getchar())&&c!=EOF);}
struct Tree
{
int fa[N][],dp[N];
void init()
{
dp[]=;
for(int i=;i<=n;i++)
{
read(fa[i][]);
dp[i]=dp[fa[i][]]+;
}
for(int i=;i<=;i++)
for(int j=;j<=n;j++)
fa[j][i]=fa[fa[j][i-]][i-];
}
}G[];
int get_ans(int x,int y)
{
int k=;
while (x!=y)
{
if (x<y)swap(x,y),k=-k;
for(int i=;i>=;i--)
if (G[k].fa[x][i]>y)x=G[k].fa[x][i];
x=G[k].fa[x][];
if (x==y)return x;
}
return x;
}
void work()
{
read(n); read(m);
G[].init(); G[].init();
int ans=;
for(int i=;i<=m;i++)
{
int x,y,t1,t2;
read(x); read(y);
x=(x+ans)%n+;
y=(y+ans)%n+;
ans=get_ans(x,y);
t1=G[].dp[x]-G[].dp[ans]+;
t2=G[].dp[y]-G[].dp[ans]+;
printf("%d %d %d\n",ans,t1,t2);
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("aa.in","r",stdin);
#endif
while()
{
work();
}
}

树链剖分+主席树

 #include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 100050
int n,m,dp2[N];
int root[N],TreeNum;
int last[N],tot;
int cnt,fa[N],dp[N],size[N],son[N],rk[N],kth[N],top[N];
struct Tree{int l,r,ls,rs,max;}tr[N*];
struct Edge{int from,to,s;}edges[N<<];
template<typename T>void read(T&x)
{
ll k=; char c=getchar();
x=;
while(!isdigit(c)&&c!=EOF)k^=c=='-',c=getchar();
if (c==EOF)exit();
while(isdigit(c))x=x*+c-'',c=getchar();
x=k?-x:x;
}
void read_char(char &c)
{while(!isalpha(c=getchar())&&c!=EOF);}
void AddEdge(int x,int y)
{
edges[++tot]=Edge{x,y,last[x]};
last[x]=tot;
}
void dfs1(int x,int pre)
{
fa[x]=pre;
dp[x]=dp[pre]+;
size[x]=;
son[x]=;
for(int i=last[x];i;i=edges[i].s)
{
Edge &e=edges[i];
if (e.to==pre)continue;
dfs1(e.to,x);
size[x]+=size[e.to];
if(size[e.to]>size[son[x]])son[x]=e.to;
}
}
void dfs2(int x,int y)
{
rk[x]=++cnt;
kth[cnt]=x;
top[x]=y;
if (son[x]==)return;
dfs2(son[x],y);
for(int i=last[x];i;i=edges[i].s)
{
Edge &e=edges[i];
if (e.to==son[x]||e.to==fa[x])continue;
dfs2(e.to,e.to);
}
}
void bt(int &x,int l,int r)
{
x=++TreeNum;
tr[x].l=l ;tr[x].r=r; tr[x].max=;
if (l==r)return;
int mid=(l+r)>>;
bt(tr[x].ls,l,mid);
bt(tr[x].rs,mid+,r);
}
void add(int &x,int last,int p,int tt)
{
x=++TreeNum;
tr[x]=tr[last];
tr[x].max=max(tr[x].max,tt);
if (tr[x].l==tr[x].r)return;
int mid=(tr[x].l+tr[x].r)>>;
if (p<=mid)add(tr[x].ls,tr[last].ls,p,tt);
else add(tr[x].rs,tr[last].rs,p,tt);
}
int query(int x,int l,int r)
{
if (l<=tr[x].l&&tr[x].r<=r)
return tr[x].max;
int mid=(tr[x].l+tr[x].r)>>,a1=,a2=;
if (l<=mid)a1=query(tr[x].ls,l,r);
if (mid<r)a2=query(tr[x].rs,l,r);
return max(a1,a2);
}
int get_ans(int x,int y)
{
int fx=x,tp,ans=;
while(x)
{
tp=query(root[y],rk[fx],rk[x]);
ans=max(ans,tp);
x=fa[fx];fx=top[x];
}
return ans;
}
void work()
{
read(n); read(m);
for(int i=;i<=n;i++)
{
read(fa[i]);
AddEdge(fa[i],i);
}
dfs1(,);
dfs2(,);
dp2[]=;
bt(root[],,n);
add(root[],root[],,);
for(int i=;i<=n;i++)
{
int y;
read(y);
dp2[i]=dp2[y]+;
add(root[i],root[y],rk[i],i);
}
int ans=;
for(int i=;i<=m;i++)
{
int x,y,t1,t2;
read(x); read(y);
x=(x+ans)%n+; y=(y+ans)%n+;
ans=get_ans(x,y);
t1=dp[x]-dp[ans]+;
t2=dp2[y]-dp2[ans]+;
printf("%d %d %d\n",ans,t1,t2);
}
}
void clear()
{
tot=; cnt=; TreeNum=;
memset(last,,sizeof(last));
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("aa.in","r",stdin);
#endif
while()
{
clear();
work();
}
}