您的位置:首页 > 其它

HDU 4718 The LCIS on the Tree 树上路径倍增

2015-08-05 10:01 555 查看


The LCIS on the Tree

Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 65535/65535 K (Java/Others)



[align=left]Problem Description[/align]
For a sequence S1, S2, ... , SN, and a pair of integers (i, j), if 1 <= i <= j <= N and Si
< Si+1 < Si+2 < ... < Sj-1 < Sj , then the sequence Si,
Si+1, ... , Sj is aCIS (Continuous Increasing Subsequence). The longestCIS of a sequence is called theLCIS (Longest
Continuous Increasing Subsequence).

Now we consider a tree rooted at node 1. Nodes have values. We have Q queries, each with two nodes u and v. You have to find the shortest path from u to v. And write down each nodes' value on the path, from u to v, inclusive. Then you will get a sequence, and
please show us the length of its LCIS.

[align=left]Input[/align]
The first line has a number T (T <= 10) , indicating the number of test cases.

For each test case, the first line is a number N (N <= 105), the number of nodes in the tree.

The second line comes with N numbers v1, v2, v3 ... , vN, describing the value of node 1 to node N. (1 <= vi <= 109)

The third line comes with N - 1 numbers p2, p3, p4 ... , pN, describing
the father nodes of node 2 to node N. Node 1 is the root and will have no father.

Then comes a number Q, it is the number of queries. (Q <= 105)

For next Q lines, each with two numbers u and v. As described above.

[align=left]Output[/align]
For test case X, output "Case #X:" at the first line.

Then output Q lines, each with an answer to the query.

There should be a blank line *BETWEEN* each test case.

[align=left]Sample Input[/align]

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


[align=left]Sample Output[/align]

Case #1:
3
2
3


[align=left]Source[/align]
2013 ACM/ICPC Asia Regional Online —— Warmup2

题意:给定一个节点数小于10w的树,每个节点有一个点权。再给出Q次询问(小于10w),每次给定一个起点和终点u,v。求在u点到v点的最短路上,节点点权的最长上连续升子段的长度。
思路:
除了数链剖分,动态树等方法之外,此题还可采用树上路径倍增的方法来解决。分别维护以某个节点,往上走2^i步之后的区间内的一些信息。这里,假设这个区间是有方向的,就是以上述的“某个”节点为起点,以上述的”往上走2^i步之后“到达的点为终点。用LL[0],LL[1]分别表示这个区间从起点(定义为左端点)开始最长连续的上升、下降子段长度,RR[0]、RR[1]表示终点为区间右端点的最长的连续上升、下降的子段长度。用ans[0]、ans[1]分别维护这个区间内连续上升、下降的长度。特别注意,这里说的上升下降,是在规定了区间起点和终点的情况下说的!
对于每一次询问,先求出u,v的lca。lca也用树上路径倍增的方法来维护和求得。维护的到u-lca段上的右连续上升和这个区间内的最长连续上升段的长度,维护得到v-lca的右连续下降长度和这个区间内的最长下降段的长度。对于lca-v这区间,颠倒起点终点之后,维护的值都代表上升的长度啦!最后答案就是u-lca的答案,v-lca的答案,两个右连续的和,这三个值取最大值。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 100005
int anc[MAXN][17];
int LL[MAXN][17][2], RR[MAXN][17][2];
int ans[MAXN][17][2];
int dep[MAXN];
struct EDGE
{
EDGE() {}
EDGE(int _to, int _next) { to = _to, next = _next; }
int to, next;
}edge[MAXN];
int edgecnt, head[MAXN];
void init()
{
memset(anc, -1, sizeof(anc));
memset(head, -1, sizeof(head));
edgecnt = 0;
}
void add(int s, int t)
{
edge[edgecnt] = EDGE(t, head[s]);
head[s] = edgecnt++;
}
int val[MAXN];
void dfs(int u, int fa, int deep)
{
anc[u][0] = fa;
dep[u] = deep;
if (val[u] < val[fa])
{
LL[u][0][0] = 2;
LL[u][0][1] = 1;
RR[u][0][0] = 2;
RR[u][0][1] = 1;
ans[u][0][0] = 2;
ans[u][0][1] = 1;
}
else if (val[u]>val[fa])
{
LL[u][0][0] = 1;
LL[u][0][1] = 2;
RR[u][0][0] = 1;
RR[u][0][1] = 2;
ans[u][0][0] = 1;
ans[u][0][1] = 2;
}
else
{
LL[u][0][0] = LL[u][0][1] = RR[u][0][0] = RR[u][0][1] = ans[u][0][0] = ans[u][0][1] = 1;
}
for (int i = head[u]; ~i; i = edge[i].next) dfs(edge[i].to, u, deep + 1);
}
void process(int n)
{
for (int j = 0; j < 16; j++)
for (int i = 1; i <= n; i++)
{
if (dep[anc[i][j]]>(1 << j))
{
anc[i][j + 1] = anc[anc[i][j]][j];
int A = anc[i][j];
int len = (1 << j) + 1;

if (LL[i][j][0] == len) LL[i][j + 1][0] = len - 1 + LL[A][j][0];
else LL[i][j + 1][0] = LL[i][j][0];

if (RR[A][j][0] == len) RR[i][j + 1][0] = len - 1 + RR[i][j][0];
else RR[i][j + 1][0] = RR[A][j][0];

if (LL[i][j][1] == len) LL[i][j + 1][1] = len - 1 + LL[A][j][1];
else LL[i][j + 1][1] = LL[i][j][1];

if (RR[A][j][1] == len) RR[i][j + 1][1] = len - 1 + RR[i][j][1];
else RR[i][j + 1][1] = RR[A][j][1];

ans[i][j + 1][0] = max(ans[i][j][0], ans[A][j][0]);
ans[i][j + 1][0] = max(ans[i][j + 1][0], RR[i][j][0] + LL[A][j][0] - 1);

ans[i][j + 1][1] = max(ans[i][j][1], ans[A][j][1]);
ans[i][j + 1][1] = max(ans[i][j + 1][1], RR[i][j][1] + LL[A][j][1] - 1);
}
}
}
int lca(int u, int v)
{
if (dep[u] < dep[v]) swap(u, v);
for (int i = 16; i >= 0; i--) if (dep[u] - (1 << i) >= dep[v]) u = anc[u][i];
if (u == v) return u;
for (int i = 16; i >= 0; i--)
if (anc[u][i] != anc[v][i])
{
u = anc[u][i];
v = anc[v][i];
}
return anc[u][0];
}
int getans(int u, int v)
{
int l = lca(u, v);
int Ru = 1, ansu = 1;
bool first = 1;
for (int i = 16; i >= 0; i--)
if (dep[u] - (1 << i) >= dep[l])
{
if (first)
{
Ru = RR[u][i][0];
ansu = ans[u][i][0];
first = 0;
u = anc[u][i];
continue;
}
ansu = max(ansu, ans[u][i][0]);
ansu = max(ansu, Ru + LL[u][i][0] - 1);

int len = (1 << i) + 1;

if (len == RR[u][i][0]) Ru += RR[u][i][0] - 1;
else Ru = RR[u][i][0];

u = anc[u][i];
}
int Rv = 1, ansv = 1;
first = 1;
for (int i = 16; i >= 0; i--)
if (dep[v] - (1 << i) >= dep[l])
{
if (first)
{
Rv = RR[v][i][1];
ansv = ans[v][i][1];
first = 0;
v = anc[v][i];
continue;
}
ansv = max(ansv, ans[v][i][1]);
ansv = max(ansv, Rv + LL[v][i][1] - 1);

int len = (1 << i) + 1;

if (len == RR[v][i][1]) Rv += RR[v][i][1] - 1;
else Rv = RR[v][i][1];

v = anc[v][i];
}
int ans = max(ansu, ansv);
ans = max(ans, Ru + Rv - 1);
return ans;
}
int main()
{
int T;
int ks = 0;
scanf("%d", &T);
bool ok = 0;
while (T--)
{
if (ok != 0)
{
puts("");
}
ok = 1;
printf("Case #%d:\n", ++ks);
int n;
scanf("%d", &n);
init();
for (int i = 1; i <= n; i++) scanf("%d", &val[i]);
for (int i = 2; i <= n; i++)
{
int s;
scanf("%d", &s);
add(s, i);
}
dfs(1, -1, 1);
process(n);
int Q;
scanf("%d", &Q);
while (Q--)
{
int u, v;
scanf("%d %d", &u, &v);
printf("%d\n", getans(u, v));
}
}
return 0;
}

这个方法,效率挺高的。



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: