HDU 5044(2014 ACM-ICPC上海网络赛)
题意:给定一个树形图,节点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上海网络赛)的更多相关文章
- hdu 4762 && 2013 ACM/ICPC 长春网络赛解题报告
这次的答案是猜出来的,如果做得话应该是应该是一个几何概型的数学题: 答案就是:n/(m^(n-1)); 具体的证明过程: 1.首先枚举这M个点中的的两个端点,概率是:n*(n-1); 2.假设这个蛋糕 ...
- hdu 4763 && 2013 ACM/ICPC 长春网络赛解题报告
一个KMP的简单题 不过好久没用过这个东东了,今天写的时候花了很多时间: 只需要花点时间判断下所有的元素都相同的的情况就行了! #include<cstdio> #include<c ...
- 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 ...
- HDU 5475(2015 ICPC上海站网络赛)--- An easy problem(线段树点修改)
题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=5475 Problem Description One day, a useless calculato ...
- HDU 4731 Minimum palindrome 2013 ACM/ICPC 成都网络赛
传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4731 题解:规律题,我们可以发现当m大于等于3时,abcabcabc……这个串的回文为1,并且字典数最小 ...
- 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的时候的个数,然后需要预处理下小 ...
- HDU 4741 Save Labman No.004 2013 ACM/ICPC 杭州网络赛
传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4741 题意:给你两条异面直线,然你求着两条直线的最短距离,并求出这条中垂线与两直线的交点. 需要注意的是 ...
- 2013 ACM/ICPC 成都网络赛解题报告
第三题:HDU 4730 We Love MOE Girls 传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4730 水题~~~ #include < ...
- 2013 ACM/ICPC 长春网络赛E题
题意:给出一个字符串,要从头.尾和中间找出三个完全相等的子串,这些串覆盖的区间互相不能有重叠部分.头.尾的串即为整个字符串的前缀和后缀.问这个相同的子串的最大长度是多少. 分析:利用KMP算法中的ne ...
- 2013 ACM/ICPC 长春网络赛F题
题意:两个人轮流说数字,第一个人可以说区间[1~k]中的一个,之后每次每人都可以说一个比前一个人所说数字大一点的数字,相邻两次数字只差在区间[1~k].谁先>=N,谁输.问最后是第一个人赢还是第 ...
随机推荐
- SPOJ QTREE 树链剖分
树链剖分的第一题,易懂,注意这里是边. #include<queue> #include<stack> #include<cmath> #include<cs ...
- c#创建ISS站点
private void CreateWebSite() { try { string installPath = "C:\\Program Files\\MyWeb"; stri ...
- Oracle自定义函数实例
1. 传入一个值, 如果该值为0,则返回空. CREATE OR REPLACE FUNCTION Fun_Test(p IN NUMBER) RETURN VARCHAR2 IS v_Result ...
- Operating System Memory Management、Page Fault Exception、Cache Replacement Strategy Learning、LRU Algorithm
目录 . 引言 . 页表 . 结构化内存管理 . 物理内存的管理 . SLAB分配器 . 处理器高速缓存和TLB控制 . 内存管理的概念 . 内存覆盖与内存交换 . 内存连续分配管理方式 . 内存非连 ...
- hihocoder #1034 毁灭者问题
传送门 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 在 Warcraft III 之冰封王座中,毁灭者是不死族打三本后期时的一个魔法飞行单位. 毁灭者的核心技能之一, ...
- HackerRank Ice Cream Parlor
传送门 Ice Cream Parlor Authored by dheeraj on Mar 21 2013 Problem Statement Sunny and Johnny together ...
- 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 ...
- 【社招】来杭州吧,阿里国际UED招前端~~
来杭州吧,阿里国际UED招前端~~ 依稀记得,几年前在北京的日子,两点一线的生活方式,似乎冲淡模糊了身边的一切,印象最深刻的莫过于北京的地铁站了吧(因为只有等地铁,搭地铁的时候,才能够停下脚步,静静地 ...
- [转载]AngularJS and scope.$apply
http://jimhoskins.com/2012/12/17/angularjs-and-apply.html http://www.cnblogs.com/zhrj000/p/3383898.h ...
- 繁华模拟赛 Vicent坐电梯
/*n<=5000这样就不能用O(n)的转移了,而是要用O(1)的转移.注意我们每次的转移都来自一个连续的区间,而且我们是求和区间求和?前缀和!令sum[step][i]表示f[ste ...