题意:给定一个树形图,节点10^5,有两种操作,一种是把某两点间路径(路径必定唯一)上所有点的权值增加一个固定值。

另一种也是相同操作,不同的是给边加权值。操作次数10^5。求操作过后,每个点和每条边的权值。

分析:此题时间卡得非常紧,最好用输入外挂,最好不要用RMQ来求解LCA。

此题是典型的在线LCA问题,先讲讲在线LCA要怎么做。

在线LCA有两种方法,第一种比较常见,即将其转化成RMQ问题。

先对树形图进行深度优先遍历,遍历过程记录路线中点的途经序列,每个非叶子节点会在序列中出现多次,从一个节点A的一个子节点回到A点再走另一个子节点的时候要再次加A加入序列。

记录序列的同时还要记录序列中每个点在树中对应的深度。以及在序列中第一次出现的位置(其实不一定非要第一个才行),主要用于根据点标号查找其在序列中对应的下标。

此时,LCA已经转化为RMQ,如果要求a,b的LCA,只需要找到a,b在遍历序列中分别对应的位置,并在深度序列中查找以这两点为端点的区间内的最小值即可。这个最小值在遍历序列中对应的点就是他们的LCA。

这种方法预处理O(NlogN),查询是O(1)。

模板如下:

//first call init_LCA(root).
//then call LCA(a, b) to quest the LCA of a and b.
//the graph can be both bidirected or unidirected.
#define MAX_NODE_NUM 0
#define MAX_EDGE_NUM 0
#define M 30 struct Edge
{
int v, next, id;
Edge()
{}
Edge(int v, int next, int id):v(v), next(next), id(id)
{}
} edge[MAX_EDGE_NUM]; int head[MAX_NODE_NUM];
int edge_cnt; void init_edge()
{
memset(head, -, sizeof(head));
edge_cnt = ;
} void add_edge(int u, int v, int id)
{
edge[edge_cnt] = Edge(v, head[u], id);
head[u] = edge_cnt++;
} bool vis[MAX_NODE_NUM];
int father[MAX_NODE_NUM];
int power[M];
int st[MAX_NODE_NUM * ][M];
int ln[MAX_NODE_NUM * ];
int seq_cnt;
int seq[*MAX_NODE_NUM];
int depth[*MAX_NODE_NUM];
int first_appearance[MAX_NODE_NUM]; //returns the index of the first minimum value in [x, y]
void init_RMQ(int f[], int n)
{
int i, j;
for (power[] = , i = ; i < ; i++)
{
power[i] = * power[i - ];
}
for (i = ; i < n; i++)
{
st[i][] = i;
}
ln[] = -;
for (int i = ; i <= n; i++)
{
ln[i] = ln[i >> ] + ;
}
for (j = ; j < ln[n]; j++)
{
for (i = ; i < n; i++)
{
if (i + power[j - ] - >= n)
{
break;
}
//for maximum, change ">" to "<"
//for the last, change "<" or ">" to "<=" or ">="
if (f[st[i][j - ]] > f[st[i + power[j - ]][j - ]])
{
st[i][j] = st[i + power[j - ]][j - ];
}
else
{
st[i][j] = st[i][j - ];
}
}
}
} int query(int x, int y)
{
if(x > y)
{
swap(x, y);
}
int k = ln[y - x + ];
//for maximum, change ">" to "<"
//for the last, change "<" or ">" to "<=" or ">="
if (depth[st[x][k]] > depth[st[y - power[k] + ][k]])
return st[y - power[k] + ][k];
return st[x][k];
} void dfs(int u ,int current_depth)
{
vis[u] = true;
first_appearance[u] = seq_cnt;
depth[seq_cnt] = current_depth;
seq[seq_cnt++] = u;
for(int i = head[u]; i != -; i = edge[i].next)
{
int v = edge[i].v;
if (vis[v])
{
continue;
}
father[v] = u;
if (!vis[v])
{
dfs(v, current_depth + );
depth[seq_cnt] = current_depth;
seq[seq_cnt++] = u;
}
}
} void init_LCA(int root)
{
memset(vis, , sizeof(vis));
father[root] = -;
seq_cnt = ;
dfs(root, );
init_RMQ(depth, seq_cnt);
} //O(1)
int LCA(int u ,int v)
{
int x = first_appearance[u];
int y = first_appearance[v];
int res = query(x, y);
return seq[res];
}

另一种方法用到了DP的思想。

用一个数组f[i][j]表示i点在树中到根节点的序列中距离i边数为2^j的点。

那么f[i][j] = f[ f[i][j - 1] ][j - 1]。

具体做法是,我们进行BFS,记录每个点的父节点,即f[i][0]。和每个点的深度。

然后根据状态转移公式填充整个数组。

在查询时,先看a,b两点谁的深度大,利用两者深度差的二进制序列,配合f数组,找到较深的点在较浅的点那层的祖先。

然后继续使用f数组,每次向上探测2^i的距离的点两者的祖先是否为同一个,如果不是则i++后继续叠加向上探测2^i,如果是同一个则i--后重新探测。直到找到最小的公共祖先为止。

这种方法预处理O(NlogN),查询是O(NlogN)。但与上一种方法相比,不需要dfs,而用bfs,这样可以节省很多时间。

模板如下:

#define MAX_NODE_NUM 0
#define MAX_EDGE_NUM 0
#define MAX_Q_LEN MAX_NODE_NUM
#define M 30 struct Edge
{
int v, next, id;
Edge()
{}
Edge(int v, int next, int id):v(v), next(next), id(id)
{}
} edge[MAX_EDGE_NUM]; int head[MAX_NODE_NUM];
int edge_cnt; void init_edge()
{
memset(head, -, sizeof(head));
edge_cnt = ;
} void add_edge(int u, int v, int id)
{
edge[edge_cnt] = Edge(v, head[u], id);
head[u] = edge_cnt++;
} bool vis[MAX_NODE_NUM];
int father[MAX_NODE_NUM][M];
int depth[MAX_NODE_NUM]; template<typename T>
class queue
{
T data[MAX_Q_LEN];
int head, rear; public:
queue()
{
head = rear = ;
} bool empty()
{
return head == rear;
} void pop()
{
head++;
if (head >= MAX_Q_LEN)
head = ;
} void push(T a)
{
data[rear++] = a;
if (rear >= MAX_Q_LEN)
rear = ;
} T front()
{
return data[head];
}
}; void bfs(int root)
{
queue<int> q;
q.push(root);
seq2_cnt = ;
while (!q.empty())
{
int u = q.front();
q.pop();
vis[u] = true;
seq2[seq2_cnt++] = u;
for (int i = head[u]; i != -; i = edge[i].next)
{
int v = edge[i].v;
if (vis[v])
{
continue;
}
father[v][] = u;
depth[v] = depth[u] + ;
q.push(v);
}
}
} //index start from 1.
void init_LCA(int root)
{
fill_n(vis, node_num + , );
memset(father, , sizeof(father));
bfs(root);
bool did;
for (int i = ; i < M; i++)
{
did = false;
for (int j = ; j <= node_num; j++)
{
int k = father[j][i - ];
if (k <= )
{
continue;
}
father[j][i] = father[k][i - ];
did = true;
}
if (!did)
{
break;
}
}
} //O(log(n))
int LCA(int x, int y)
{
if (depth[x] > depth[y])
{
swap(x, y);
}
int diff = depth[y] - depth[x];
for (int i = ; i < M && diff; i++)
{
if (diff & )
{
y = father[y][i];
}
diff >>= ;
}
if (x == y)
{
return x;
}
int exp = ;
while (x != y)
{
if (!exp || father[x][exp] != father[y][exp])
{
x = father[x][exp];
y = father[y][exp];
exp++;
}else
{
exp--;
}
}
return x;
}

再说说这题是怎么做的。將种操作进行一下转化,认为每次加权操作都是分别向由两点到他们的LCA的路径加权。

还可以进行进一步的转化设a,b为要加权的路径两端点,他们的LCA为c,根节点为root。

那么该加权操作可转化为由a和b到root分别加权,再由c到root加两倍的负权。这样正负抵消后与原操作等价。

这样转化之后,所有的操作都便成了到根节点的操作,那么只需要將所有的操作标记在非根节点的另一个点上,然后自底向上把操作树中的每个点,將该点的子节点中的权值操作向上传递即可。

本题不可以使用第一种方法,会超时,可能是dfs太耗时。用第二种方法虽然查询时间稍慢,但是通过了。

代码如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std; #define MAX_NODE_NUM 100005
#define MAX_EDGE_NUM MAX_NODE_NUM * 2
#define MAX_Q_LEN MAX_NODE_NUM
#define M 30
#define D(x) struct Edge
{
int v, next, id;
Edge()
{}
Edge(int v, int next, int id):v(v), next(next), id(id)
{}
} edge[MAX_EDGE_NUM]; int head[MAX_NODE_NUM];
int edge_cnt; void init_edge()
{
memset(head, -, sizeof(head));
edge_cnt = ;
} void add_edge(int u, int v, int id)
{
edge[edge_cnt] = Edge(v, head[u], id);
head[u] = edge_cnt++;
} int node_num, opr_num;
long long edge_opr[MAX_NODE_NUM];
long long node_opr[MAX_NODE_NUM];
bool vis[MAX_NODE_NUM];
long long ans_edge[MAX_EDGE_NUM];
int father[MAX_NODE_NUM][M];
int depth[MAX_NODE_NUM];
int seq2[MAX_NODE_NUM];
int seq2_cnt; template<typename T>
class queue
{
T data[MAX_Q_LEN];
int head, rear; public:
queue()
{
head = rear = ;
} bool empty()
{
return head == rear;
} void pop()
{
head++;
if (head >= MAX_Q_LEN)
head = ;
} void push(T a)
{
data[rear++] = a;
if (rear >= MAX_Q_LEN)
rear = ;
} T front()
{
return data[head];
}
}; void bfs(int root)
{
queue<int> q;
q.push(root);
seq2_cnt = ;
while (!q.empty())
{
int u = q.front();
q.pop();
vis[u] = true;
seq2[seq2_cnt++] = u;
for (int i = head[u]; i != -; i = edge[i].next)
{
int v = edge[i].v;
if (vis[v])
{
continue;
}
father[v][] = u;
depth[v] = depth[u] + ;
q.push(v);
}
}
} //index start from 1.
void init_LCA(int root)
{
fill_n(vis, node_num + , );
memset(father, , sizeof(father));
bfs(root);
bool did;
for (int i = ; i < M; i++)
{
did = false;
for (int j = ; j <= node_num; j++)
{
int k = father[j][i - ];
if (k <= )
{
continue;
}
father[j][i] = father[k][i - ];
did = true;
}
if (!did)
{
break;
}
}
} int LCA(int x, int y)
{
if (depth[x] > depth[y])
{
swap(x, y);
}
int diff = depth[y] - depth[x];
for (int i = ; i < M && diff; i++)
{
if (diff & )
{
y = father[y][i];
}
diff >>= ;
}
if (x == y)
{
return x;
}
int exp = ;
while (x != y)
{
if (!exp || father[x][exp] != father[y][exp])
{
x = father[x][exp];
y = father[y][exp];
exp++;
}else
{
exp--;
}
}
return x;
} inline int read_int()
{
int num = ;
int sign = ;
bool skip = false;
int c = ;
while((c = getchar()) != EOF)
{
if(c == '-')
{
sign = -;
skip = true;
}
else if(c >= '' && c <= '')
{
num = num * + c - '';
skip = true;
}
else if(skip)
{
break;
}
}
return num * sign;
} inline int ReadOP()
{
int c = ;
while((c = getchar()) != EOF && c != 'A');
getchar(); getchar();
return getchar();
} void input()
{
scanf("%d%d", &node_num, &opr_num);
for (int i = ; i < node_num - ; i++)
{
int a, b;
a = read_int();
b = read_int();
add_edge(a, b, i);
add_edge(b, a, i);
}
init_LCA();
fill_n(edge_opr, node_num + , );
fill_n(node_opr, node_num + , );
fill_n(ans_edge, node_num + , );
for (int i = ; i < opr_num; i++)
{
int a, b, k;
int op = ReadOP();
a = read_int();
b = read_int();
k = read_int();
int c = LCA(a, b);
D(printf("%d\n", c));
if (op == '')
{
edge_opr[c] -= k * ;
edge_opr[a] += k;
edge_opr[b] += k;
}else
{
node_opr[c] -= k;
if (father[c][] > )
{
node_opr[father[c][]] -= k;
}
node_opr[a] += k;
node_opr[b] += k;
}
}
} void work()
{
for (int i = seq2_cnt - ; i >= ; i--)
{
int u = seq2[i];
D(printf("%d %lld\n", u, node_opr[u]));
for (int j = head[u]; j != -; j = edge[j].next)
{
int v = edge[j].v;
if (v == father[u][])
{
continue;
}
node_opr[u] += node_opr[v];
edge_opr[u] += edge_opr[v];
ans_edge[edge[j].id] = edge_opr[v];
}
D(printf("%d %lld\n", u, node_opr[u]));
}
} void output()
{
bool first = true;
for (int i = ; i <= node_num; i++)
{
if (first)
{
first = false;
}else
{
putchar(' ');
}
printf("%lld", node_opr[i]);
}
puts("");
first = true;
for (int i = ; i < node_num - ; i++)
{
if (first)
{
first = false;
}else
{
putchar(' ');
}
printf("%lld", ans_edge[i]);
}
puts("");
} int main()
{
int t;
scanf("%d", &t);
for (int i = ; i < t; i++)
{
printf("Case #%d:\n", i + );
init_edge();
seq2_cnt = ;
input();
work();
output();
}
return ;
}

HDU 5044(2014 ACM-ICPC上海网络赛)的更多相关文章

  1. hdu 4762 && 2013 ACM/ICPC 长春网络赛解题报告

    这次的答案是猜出来的,如果做得话应该是应该是一个几何概型的数学题: 答案就是:n/(m^(n-1)); 具体的证明过程: 1.首先枚举这M个点中的的两个端点,概率是:n*(n-1); 2.假设这个蛋糕 ...

  2. hdu 4763 && 2013 ACM/ICPC 长春网络赛解题报告

    一个KMP的简单题 不过好久没用过这个东东了,今天写的时候花了很多时间: 只需要花点时间判断下所有的元素都相同的的情况就行了! #include<cstdio> #include<c ...

  3. HDU 5000 2014 ACM/ICPC Asia Regional Anshan Online DP

    Clone Time Limit : 2000/1000ms (Java/Other)   Memory Limit : 65536/65536K (Java/Other) Total Submiss ...

  4. HDU 5475(2015 ICPC上海站网络赛)--- An easy problem(线段树点修改)

    题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=5475 Problem Description One day, a useless calculato ...

  5. HDU 4731 Minimum palindrome 2013 ACM/ICPC 成都网络赛

    传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4731 题解:规律题,我们可以发现当m大于等于3时,abcabcabc……这个串的回文为1,并且字典数最小 ...

  6. HDU 4734 F(x) 2013 ACM/ICPC 成都网络赛

    传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4734 数位DP. 用dp[i][j][k] 表示第i位用j时f(x)=k的时候的个数,然后需要预处理下小 ...

  7. HDU 4741 Save Labman No.004 2013 ACM/ICPC 杭州网络赛

    传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4741 题意:给你两条异面直线,然你求着两条直线的最短距离,并求出这条中垂线与两直线的交点. 需要注意的是 ...

  8. 2013 ACM/ICPC 成都网络赛解题报告

    第三题:HDU 4730 We Love MOE Girls 传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4730 水题~~~ #include < ...

  9. 2013 ACM/ICPC 长春网络赛E题

    题意:给出一个字符串,要从头.尾和中间找出三个完全相等的子串,这些串覆盖的区间互相不能有重叠部分.头.尾的串即为整个字符串的前缀和后缀.问这个相同的子串的最大长度是多少. 分析:利用KMP算法中的ne ...

  10. 2013 ACM/ICPC 长春网络赛F题

    题意:两个人轮流说数字,第一个人可以说区间[1~k]中的一个,之后每次每人都可以说一个比前一个人所说数字大一点的数字,相邻两次数字只差在区间[1~k].谁先>=N,谁输.问最后是第一个人赢还是第 ...

随机推荐

  1. SPOJ QTREE 树链剖分

    树链剖分的第一题,易懂,注意这里是边. #include<queue> #include<stack> #include<cmath> #include<cs ...

  2. c#创建ISS站点

    private void CreateWebSite() { try { string installPath = "C:\\Program Files\\MyWeb"; stri ...

  3. Oracle自定义函数实例

    1. 传入一个值, 如果该值为0,则返回空. CREATE OR REPLACE FUNCTION Fun_Test(p IN NUMBER) RETURN VARCHAR2 IS v_Result ...

  4. Operating System Memory Management、Page Fault Exception、Cache Replacement Strategy Learning、LRU Algorithm

    目录 . 引言 . 页表 . 结构化内存管理 . 物理内存的管理 . SLAB分配器 . 处理器高速缓存和TLB控制 . 内存管理的概念 . 内存覆盖与内存交换 . 内存连续分配管理方式 . 内存非连 ...

  5. hihocoder #1034 毁灭者问题

    传送门 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 在 Warcraft III 之冰封王座中,毁灭者是不死族打三本后期时的一个魔法飞行单位. 毁灭者的核心技能之一, ...

  6. HackerRank Ice Cream Parlor

    传送门 Ice Cream Parlor Authored by dheeraj on Mar 21 2013 Problem Statement Sunny and Johnny together ...

  7. ON THE EVOLUTION OF MACHINE LEARNING: FROM LINEAR MODELS TO NEURAL NETWORKS

    ON THE EVOLUTION OF MACHINE LEARNING: FROM LINEAR MODELS TO NEURAL NETWORKS We recently interviewed ...

  8. 【社招】来杭州吧,阿里国际UED招前端~~

    来杭州吧,阿里国际UED招前端~~ 依稀记得,几年前在北京的日子,两点一线的生活方式,似乎冲淡模糊了身边的一切,印象最深刻的莫过于北京的地铁站了吧(因为只有等地铁,搭地铁的时候,才能够停下脚步,静静地 ...

  9. [转载]AngularJS and scope.$apply

    http://jimhoskins.com/2012/12/17/angularjs-and-apply.html http://www.cnblogs.com/zhrj000/p/3383898.h ...

  10. 繁华模拟赛 Vicent坐电梯

    /*n<=5000­这样就不能用O(n)的转移了,而是要用O(1)的转移.­注意我们每次的转移都来自一个连续的区间,而且我们是求和­区间求和?­前缀和!­令sum[step][i]表示f[ste ...