NOIP 2015 Senior 6
来源:互联网 发布:网络暴力 论文 编辑:程序博客网 时间:2024/06/10 17:20
传送门(洛谷)
①求最小值/最大值 => 二分答案进行判断
在 NOIP 中,经常会出现求最小时间的问题。当直接解决走不通时,可以考虑二分一个答案,然后判断一下当前答案是否可行。对于这道题而言,可以通过二分答案来解决。
②一般思路
由于最后要求的是航线最大值的最小值,所以要对答案有贡献,边权被置为0的边必须为某些航线的交集,使对这条边置0后所有航线的最大路程至多为之前的最大值减去改造成虫洞的边权。直接解决问题的难点就在于如何确定哪些航线应该属于被优化的航线,哪些不属于;而二分答案后就很明显了:如果总时间小于等于答案,就不需要属于被优化的航线,否则一定属于被优化的航线。这也是想到二分的突破口。
知道哪些航线需要被优化后,我们就可以求交集了。最后的选择就是在所有需要被优化的航线的交集中选一条边权最大的边。如果所有航线的最大值(当然也是被优化的航线的最大值)减去被选择的边的边权小于等于答案,那么答案可行。
③求交集
一个正常的思路就是每走过一条边,就给这条边的标记+1。走完所有航线后,如果某一条边的标记次数为走过的航线次数,那么那一条边就属于交集。
然而,直接打标记显然不靠谱,所以这里要用更高级的方法。打树剖从原理上来说可以,但联赛不考树剖,而且树剖的常数也是比较大的,所以在这里不讨论树剖。
可以借助数组前缀和的思想。对于数组
求前缀和的过程可以直接DFS。
④通过预处理拓扑序加速计算前缀和
相同数量级的操作用DFS的速度肯定不及从头到尾扫过一次数组的速度。事实上,这个题直接用DFS最后一个点容易被卡掉,但是用拓扑排序预处理后就能侥幸通过。其原理是用拓扑排序处理后保证使用到某一个结点的值时其值一定被它的子结点更新完了。这个思想很重要,应用也很广泛。
参考代码
#include <cstdio>#include <cstdlib>#include <cmath>#include <cstring>#include <iostream>#include <algorithm>#include <vector>#include <string>#include <stack>#include <queue>#include <deque>#include <map>#include <set>#include <bitset>using std::cin;using std::cout;using std::endl;typedef int INT;inline INT readIn(){ INT a = 0; bool minus = false; char ch = getchar(); while (!(ch == '-' || ch >= '0' && ch <= '9')) ch = getchar(); if (ch == '-') { minus = true; ch = getchar(); } while (ch >= '0' && ch <= '9') { a *= 10; a += ch; a -= '0'; ch = getchar(); } if (minus) a = -a; return a;}const INT maxn = 300005;INT n, m;struct Edge{ INT to; INT cost; INT next;} edges[maxn * 2];INT head[maxn];void addEdge(INT from, INT to, INT cost){ static INT count_ = 0; count_++; edges[count_].to = to; edges[count_].cost = cost; edges[count_].next = head[from]; head[from] = count_;}INT topo[maxn];INT size;INT depth[maxn];INT next[20][maxn];INT sum[20][maxn];void dfs(INT node = 1){ for(int i = head[node]; i; i = edges[i].next) { INT to = edges[i].to; if(to == next[0][node]) continue; INT cost = edges[i].cost; depth[to] = depth[node] + 1; next[0][to] = node; sum[0][to] = cost; dfs(to); } topo[++topo[0]] = node;}void goDouble() //这道题的路径长和LCA可以用倍增来处理{ while(1 << size < n) size++; for(int i = 1; i <= size; i++) { for(int j = 1; j <= n; j++) { next[i][j] = next[i - 1][next[i - 1][j]]; sum[i][j] = sum[i - 1][j] + sum[i - 1][next[i - 1][j]]; } }}struct Path{ INT from; INT to; INT lca; INT t; Path() {} Path(INT u, INT v) : from(u), to(v), lca(0), t(0) { if(depth[u] < depth[v]) std::swap(u, v); for(int i = size; ~i; i--) { if(depth[next[i][u]] < depth[v]) continue; t += sum[i][u]; u = next[i][u]; } if(u != v) { for(int i = size; ~i; i--) { if(next[i][u] == next[i][v]) continue; t += sum[i][u] + sum[i][v]; u = next[i][u]; v = next[i][v]; } t += sum[0][u] + sum[0][v]; u = next[0][u]; } lca = u; }} paths[maxn];INT maxPath;INT tree[maxn];INT goSum(){ for(int i = 1; i <= n; i++) { tree[next[0][topo[i]]] += tree[topo[i]]; //使用预处理好的拓扑序加速 }}bool check(INT s){ memset(tree, 0, sizeof(tree)); INT total = 0; for(int i = 1; i <= m; i++) { if(paths[i].t <= s) continue; total++; tree[paths[i].from]++; tree[paths[i].to]++; tree[paths[i].lca] -= 2; } goSum(); INT maxRemain = 0; if(total) { for(int i = 1; i <= n; i++) { if(tree[i] == total) { maxRemain = std::max(maxRemain, sum[0][i]); } } } return maxPath - maxRemain <= s;}void run(){ n = readIn(); m = readIn(); for(int i = 1; i < n; i++) { INT from = readIn(); INT to = readIn(); INT cost = readIn(); addEdge(from, to, cost); addEdge(to, from, cost); } depth[1] = 1; dfs(); goDouble(); for(int i = 1; i <= m; i++) { INT from = readIn(); INT to = readIn(); new(paths + i) Path(from, to); maxPath = std::max(maxPath, paths[i].t); } INT l = -1, r = maxPath; while(r - l > 1) { INT mid = l + ((r - l) >> 1); if(check(mid)) { r = mid; } else { l = mid; } } cout << r << endl;}int main(){#ifndef JUDGE freopen("transport.in", "r", stdin); freopen("transport.out", "w", stdout);#endif run(); return 0;}
总结:
这道题虽然说思维难度不大,但其实是很多经典的算法(不如说想法)合并而成的。他们有:
①极值问题通过二分答案转换为判定问题
②通过倍增求树上的路径的长度及LCA(这个也算一个吧)
③前缀和思想在树上的应用,以及差分标记
④通过预处理拓扑序来加速DAG(Tree)上的DP(递推)
- NOIP 2015 Senior 6
- NOIP 2015 Senior 2
- NOIP 2015 Senior 3
- NOIP 2015 Senior 5
- NOIP 2015 Senior 4
- NOIP 2011 Senior 6
- NOIP 2014 Senior 6
- NOIP 2016 Senior 6
- NOIP 2013 Senior 6
- NOIP 2017 Senior 6
- NOIP 2009 Senior 1
- NOIP 2009 Senior 4
- NOIP 2009 Senior 3
- NOIP 2011 Senior 2
- NOIP 2011 Senior 3
- NOIP 2011 Senior 4
- NOIP 2011 Senior 5
- NOIP 2012 Senior 2
- 【CUGBACM15级BC第9场 A】hdu 4993 Revenge of ex-Euclid
- python 函数参数类型
- Linux的帮助命令
- JavaScript中valueOf 、toString 、toLocalString的区别
- 阿里云服务器部署个人博客
- NOIP 2015 Senior 6
- Linux上iptables防火墙的基本应用教程
- Android无限自动轮播+自动添加原点+监听改变原点
- JVM自带性能分析工具介绍——JPS
- spring boot+mybatis 多数据源切换
- 获取文件大小
- poj 3301 Texas Trip 题解
- MySql学习笔记(一)
- 棋盘覆盖【递归分治法】