题意:给定一个树形图,节点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. c++重载运算符注意

    c++重载运算符的时候加&或不加: 如果加了&表示引用,说明用的都是同一块内存.如果不加,那么用的就是一份拷贝,即不同的内存. 一般连续操作的时候要加&. 可以重新定义一个对象 ...

  2. 【CodeForces 624C】Graph and String

    题 题意 n个表示abc三个字符的点,所有a和b是相连的,所有b和c是相连的,所有相同的是相连的,现在给你n个点和他们之间的m条边,判断是否存在这样的字符串,存在则给出一个符合条件的. 分析 我的做法 ...

  3. 记录一次MVC 3.0错误 HTTP 404您正在查找的资源(或者它的一个依赖项)可能已被移除,或其名称已更改,或暂时不可用。请检查以下 URL 并确保其拼写正确。

    在部署到IIS7时,MVC3报了一个找不到资源的错误,文件肯定是有的,而且页面是肯定报错的,也就说内部运行错误了,而MVC把错误没有抛出来而已: 所以对症下药,发觉我的项目里面用了rexs进行多语言, ...

  4. eclipse中建python项目并运行

    1. Help → Install New Software 2.Enter http://pydev.org/updates 3.点击Click "Next" and " ...

  5. JSTL标签库简介

    核心标签库 http://java.sun.com/jsp/jstl/core <c:catch>,<c:url>的使用 <!-- 捕获异常 --> <c:c ...

  6. linux桌面应用开发之折腾

    1 起因 需要开发一个wifi定位的应用,最先在android下搞,后来因为多网卡的原因要换平台,经历了windows,最终选择用kaili linux.debian系的linux,开发桌面应用怎么办 ...

  7. 初学Struts2-自定义拦截器及其配置

    自定义拦截器,首先新建一个继承自AbstractInterceptor类的类,然后重写intercept方法,代码如下 public class HelloInterceptor extends Ab ...

  8. Advice for applying Machine Learning

    https://jmetzen.github.io/2015-01-29/ml_advice.html Advice for applying Machine Learning This post i ...

  9. Protocol Buffers(Protobuf)开发者指南---概览

    Protocol Buffers(Protobuf)开发者指南---概览 欢迎来到protocol buffers的开发者指南文档,protocol buffers是一个与编程语言无关‘.系统平台无关 ...

  10. 让Windows 7内置Administrator 用户也能使用指纹登录

    前言 这周末重装了个系统,之前用windows 8 现在8.1预览版出来了,琢磨着是不是给升级玩玩.装上后感觉变化不大,后来一折腾,就换回windows 7 了(64位旗舰版).将安装时创建的用户删除 ...