题意:给定一个树形图,节点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. Java基础-gs(垃圾回收)

    Java垃圾回收概况 Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代 ...

  2. Log4Net使用详解

    1.Log4Net环境的搭建与基本配置 (1)Log4Net框架介绍     Log4net 是 Apache 下一个开放源码的项目,它是Log4j 的一个克隆版.我们可以控制日志信息的输出目的地.L ...

  3. shell中一维数组值得获取

    (1)数组的定义 root@tcx4440-03:~# a=(1 2 3 4) root@tcx4440-03:~# echo ${a[1]}2 root@tcx4440-03:~# a[0]=1ro ...

  4. 最短路算法floyd

    内容: 对n个点(n<=450),已知他们的边,也就是相邻关系,求任意两个点的最短距离. 代码: for(int k=1; k<=n; k++)//k写在外面 for(int i=1; i ...

  5. BZOJ-3524 Couriers 可持久化线段树

    可持久化线段树,其实就是类主席树了.. 3524: [Poi2014]Couriers Time Limit: 20 Sec Memory Limit: 128 MB Submit: 1124 Sol ...

  6. APNs详细使用步骤

    1. 什么是推送通知 消息通知分本地通知和远程推送通知,是没有运行在前台的应用程序可以让它们的用户获得相关消息通知的方式.消息通知可能是一条消息,即将发生的日历事件,或远程服务器的新数据.当被操作系统 ...

  7. eclipse中Preferences的一些设置

    1.在Eclipse里面设置了java文件保存时自动格式化,在java->Code Style->Formatter里设置了自定义的格式化的样式,这样每次保存后都会自动格式化代码,用了一段 ...

  8. 高德地图3D版的使用方法

    坐标拾取器: http://lbs.amap.com/console/show/picker 其经纬度与LatLng()方法中的经纬度是对调的 SDK和实例下载: http://lbs.amap.co ...

  9. 使用jQuery解析JSON数据(由ajax发送请求到php文件处理数据返回json数据,然后解析json写入html中呈现)

    在上一篇的Struts2之ajax初析中,我们得到了comments对象的JSON数据,在本篇中,我们将使用jQuery进行数据解析. 我们先以解析上例中的comments对象的JSON数据为例,然后 ...

  10. The Dirichlet Distribution 狄利克雷分布 (PRML 2.2.1)

    The Dirichlet Distribution 狄利克雷分布 (PRML 2.2.1) Dirichlet分布可以看做是分布之上的分布.如何理解这句话,我们可以先举个例子:假设我们有一个骰子,其 ...