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,谁输.问最后是第一个人赢还是第 ...
随机推荐
- Xunit
Attributes Note: This table was written back when xUnit.net 1.0 has shipped, and needs to be updated ...
- hdu1828 线段树+离散化+扫描线
添加lb[],rb[]数组,来标记竖边.添加num,来计算竖边的个数,因为计算周长的时候,未覆盖的竖边都要加. #include<stdio.h> #include<stdlib.h ...
- Windows python3.3下安装BeautifulSoup
首先在官网下载:http://www.crummy.com/software/BeautifulSoup/#Download BeautifulSoup在版本4以上都开始支持python3了,所以就下 ...
- Intel 80x86 Linux Kernel Interrupt(中断)、Interrupt Priority、Interrupt nesting、Prohibit Things Whthin CPU In The Interrupt Off State
目录 . 引言 . Linux 中断的概念 . 中断处理流程 . Linux 中断相关的源代码分析 . Linux 硬件中断 . Linux 软中断 . 中断优先级 . CPU在关中断状态下编程要注意 ...
- 表单form action的url写法
在写web页面时,标签 是很常见的元素,它的一个属性是action,用来标识将表单交给谁去处理.很显然,这里有一个地址的问题,而且是在服务器这边的地址.比如服务器内的一个servlet. 那么这个 ...
- UVALive 6523 Languages
传送门 The Enterprise has encountered a planet that at one point had been inhabited. The only remnant f ...
- Jquery easyui datagrid 删除多行问题
http://www.cnblogs.com/Dtscal/archive/2012/07/04/2576639.html 最近模仿了刘冬大哥的<开源框架完美组合之Spring.NET + NH ...
- structs2标签简单实用,及自定义转换器示例代码
一.在structs.xml中配置 <structs> <package name="tagp" namespace="/test" exte ...
- JDK安装成功了,环境变量也配置好了,测试代码也可以运行,但是打不开eclipse
解决办法:删除eclipse,重新解压后,将JDK文件夹下的jre文件夹拷贝到eclipse文件夹下,OK
- sqlmap注入检测
1.列出可利用数据库: sqlmap -u url --dbs 2.列出某个数据库中表: sqlmap -u url --tables -D south sqlmap -u ur ...