*POJ 3417 - Network(LCA + 树形DP)
来源:互联网 发布:修罗场是什么意思网络 编辑:程序博客网 时间:2024/05/04 15:28
题目:
http://poj.org/problem?id=3417
题意:
给出一棵有n个节点的树,新加入m条边,求出断开一条旧边和一条心边,使得图变成两个连通块的方案数。
思路:
(摘抄)
我们知道,这m条边连上后这颗树必将成环,假设新边为(u,v),那么环为u---->LCA(u,v)------->v-------->u,我们给这个环上的边计数1,表示这些边被一个环覆盖了一次。添加了多条新边后,可知树上有些边是会被多次覆盖的,画图很容易发现,但一个树边被覆盖了2次或以上,它就是一条牢固的边,就是说毁掉它再毁掉任何一条新边都好,树都不会断裂,这个结论也是很容易证明的,画图更明显,所以不累述
所以这启发了我们,要统计所有的边被覆盖了几次,我们分情况来讨论
1.覆盖0次,说明这条边不在任何一个环上,这样的边最脆弱,单单是毁掉它就已经可以使树断裂了,这时候只要任意选一条新边去毁,树还是断裂的,所以这样的树边,就产生m种方案(m为新边条数)
2.覆盖1次,说明这条边在一个环上,且,仅在一个环上,那么要使树断裂,就毁掉这条树边,并且毁掉和它对应的那条新边(毁其他的新边无效),就一定能使树断裂,这种树边能产生的方案数为1,一条这样的树边只有唯一解
3.覆盖2次或以上,无论怎么样都不能使树断裂,产生的方案数为0
所以,如果我们能知道所有的树边的覆盖,那么统计一次就行了,所以问题只剩下,怎么每条边被覆盖了几次?
需要用到树DP。
首先我们定义dp[u]的意义为,u所对应的那条父边(u和它父亲连接的那条边)被覆盖的次数
对应一条新边(u,v),我们知道是要求LCA(u,v)的,这时候我们计数dp[u]++ , dp[v]++ , dp[lca]-=2
为什么这样计数?我们试着看看,点u和点v和点lca,都试着沿路径一直回到树根处(注意不是回到LCA而是树根),u的路径中每经过一个点,就将这些点上的值加上dp[u],同样v的路径上没经过一个点就将这些点上的值加上dp[v],lca也是这样。你会发现,lca回到树根的部分,其实被抵消掉了,dp值没有变化,而u到lca,v到lca部分的值都已经分别加上了dp[u],dp[v]
这启发了我们,我们在求完所有m对顶点的LCA后,每个u和v都做一次dp[u]++,dp[v]++,dp[lca]-=2,然后我从树根开始向下遍历一次整棵树,在回溯的时候就执行累加dp[u],dp[v]的操作
AC.
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>#pragma comment(linker,"/STACK:1024000000,1024000000")using namespace std;const int maxn = 1e5+5;int n, m;int dp[maxn];int tot, head[maxn];struct Edge { int to, next;}edge[2*maxn];void addedge(int u, int v){ edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++;}int tt, h[maxn];struct New { int to, id, next;}newg[2*maxn];void addnew(int u, int v, int id){ newg[tt].to = v; newg[tt].id = id; newg[tt].next = h[u]; h[u] = tt++;}int par[maxn];int find(int x){ if(par[x] == x) return x; return par[x] = find(par[x]);}int vis[maxn], lca[maxn], res[maxn];void Tarjan(int u){ vis[u] = 1; for(int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if(!vis[v]) { Tarjan(v); par[v] = u; } } for(int i = h[u]; ~i; i = newg[i].next) { int v = newg[i].to, id = newg[i].id; if(vis[v]) { res[id] = find(v); } }}void dfs(int u){ vis[u] = 1; for(int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if(!vis[v]) { dfs(v); dp[u] += dp[v]; } }}void init(){ tot = 0; memset(head, -1, sizeof(head)); tt = 0; memset(h, -1, sizeof(h)); memset(dp, 0, sizeof(dp)); memset(vis, 0, sizeof(vis)); for(int i = 1; i <= n; ++i) par[i] = i;}int main(){ //freopen("in", "r", stdin); while(~scanf("%d %d", &n, &m)) { init(); int u, v; for(int i = 0; i < n-1; ++i) { scanf("%d %d", &u, &v); addedge(u, v); addedge(v, u); } for(int i = 0; i < m; ++i) { scanf("%d %d", &u, &v); dp[u]++; dp[v]++; addnew(u, v, i); addnew(v, u, i); } Tarjan(1); for(int i = 0; i < m; i++) { int lca = res[i]; dp[lca] -= 2; } memset(vis, 0, sizeof(vis)); dfs(1); int ans = 0; for(int i = 2; i <= n; ++i) { if(dp[i] == 1) ans++; if(dp[i] == 0) ans += m; } printf("%d\n", ans); } return 0;}
- *POJ 3417 - Network(LCA + 树形DP)
- poj 3417 Network LCA+树形dp
- POJ 题目3417 Network(LCA转RMQ+树形DP)
- POJ 3417 Network(在线倍增LCA+树形DP)
- 【LCA+树形DP】POJ 3417
- poj 3417--LCA+树形dp
- POJ 3417 lca+树形dp
- POJ 3417 Network (LCA + DP)
- POJ - 3417 Network(LCA + DP)
- POJ 3417Network(dp+LCA)
- POJ 3417 Network(dp+tarjian LCA)
- poj3417 Network 离线LCA + 树形dp
- POJ 3417 Network LCA
- 【LCA】 POJ 3417 Network 记数
- tarjan -LCA POJ-3417-Network
- [树形DP] poj 3659 Cell Phone Network
- poj3417 Network LCA+DP
- POJ 3659 Cell Phone Network(树形DP)
- 安卓使用ImageView显示OpenCV-Mat
- django静态文件配置
- 程序员如何爱护自己的眼睛
- django静态文件配置
- Wooden Sticks
- *POJ 3417 - Network(LCA + 树形DP)
- 在web.xml中classpath和classpath*的区别。/与/*的区别
- 1.webservice及其简单应用!
- django静态文件配置
- 鸟哥私房菜学习笔记三
- django静态文件配置
- django静态文件配置
- Android当无线鼠标,通过蓝牙与pc通信,pc端用java写
- django静态文件配置