zoukankan      html  css  js  c++  java
  • 为了表明我还活着

    目录


      SSSP(Single Sourse Shortest Path) 问题

      dijkstra

      void dijkstra() {
      	memset(dis, 0x3f, sizeof dis);
      	memset(vis, 0, sizeof vis);
      	dis[1] = 0;
      	queue.push(make_pair(0,1));
      	while(queue.size()) {
      		int x=q.top().second(); q.pop();
      		if(vis[x]) continue;
      		vis[x] = true;
      		for(int i=head[x]; i; i=next[i]) {
      			int y=ver[i], z=weight[i];
      			if(dis[y] > dis[x]+z) {
      				dis[y] = dis[x]+z;
      				queue.push( make_pair(-dis[y],y) );
      			}
      		}
      	}
      }
      

      队列优化 Bellman-Ford

      void spfa() {
      	memset(dis, 0x3f, sizeof dis);
      	memset(inqueue, 0, sizeof inqueue);
      	dis[1]=0, inqueue[1]=true;
      	queue.push(1);
      	while(queue.size()) {
      		int x=queue.front(); queue.pop();
      		inqueue[x] = false;
      		for(int i=head[x]; i; i=next[i]) {
      			int y=ver[i], z=weight[i];
      			if(dis[y] > dis[x]+z) {
      				dis[y] = dis[x]+z;
      				if(!inqueue[y]) queue.push(y), inqueue[y]=true;
      			}
      		}
      	}
      }
      

      POJ3662

      解法1 :图论建模+最短路

      // 过于简单, 不写。
      

      解法2:二分答案+双端队列 BFS

      // 重点是双端队列 BFS 解边权只有 0/1 的最短路, 理解这个算法需要对 BFS 时队列里节点之间的数据特点有适当的了解, 在此不叙。
      

      CH6101

      本题的重点是在对题目的分析上, 代码编写上对于初学者的难度主要集中在各种重要的细节上, 是很好的入门题。

      BZOJ2200

      本题的分析比较简单, 重点是在代码上, 即使是对于水平较差的提高选手也值得一写。


      APSP(All-pairs shortest path) 问题

      Floyd

      [ ext{dis[k,i,j] = min(dis[k-1,i,j], dis[k-1,i,k]+dis[k-1,k,j])} ]

      其中,dis[k,i,j] 表示从 i 到 j, 路径上除了两端点编号不超过 k, 得到的最短路的长度。

      void Floyd() {
      	for(int k=1; k<=n; ++k)
      		for(int i=1; i<=n; ++i)
      			for(int j=1; j<=n; ++j)
      				dis[i][j] = min(dis[i][j], dis[i][k]+dis[k][j]);
      }
      

      POJ1094

      本体的重点是了解各种常见关系(如偏序关系)的特征。

      POJ1734

      本题对于真正的“思考”上的初学者, 是一道不错的练习题, 同时也助于初识 Floyd 算法的人加深理解。

      Social Net Work

      本题也有助于加深对 Floyd 一些特性的记忆。

      简单说一下:对于 dis(i,j), 用不同的 k 来更新, 所涉及的路径彼此没有交集。


      生成树相关

      定理

      1.任意一颗最小生成树一定包含无向图中最小的边

      推论:切割性质

      将原图的一个生成森林加边变成生成树, 要求边权和尽量小, 则这颗生成树一定包含连接两个不联通的子图的边中的最小的边

      Kruskal 基于上述推论, 维护图的最小生成森林, 时间复杂度为 O(m log m) 。

      Prim 算法也基于上述推论, 但它的思路是维护图的最小生成树的一部分, 时间复杂度为 O(m log n), 显然, Prim 在稠密图上效果优于 Kruskal 。

      // 未加堆优化, 复杂度 O(n^2)
      void prim() {
      	memset(dis, 0x3f, sizeof dis);
      	memset(vis, 0, sizeof vis);
      	dis[1] = 0;
      	for(int i=1; i<n; ++i) {
      		int x = 0;
      		for(int j=1; j<=n; ++j)
      			if(!vis[j] && (x==0||d[j]<d[x]) ) x=j;
      		vis[x] = 1;
      		for(int y=1; y<=n; ++y)
      			if(!vis[y]) d[y] = min(d[y], a[x][y]);
      	}
      }
      

      最小瓶颈生成树

      一棵生成树, 最大边权尽量小。

      最小生成树就是一个最小瓶颈生成树; 不是所有的最小瓶颈生成树都是最小生成树。

      最小瓶颈路

      求一条 u->v 的路径,最大边尽量小。

      答案就是在最小瓶颈生成树上的唯一路径。

      CH6201

      单独考虑每一条新加进去的边, 都与原树构成一个环, 为使总边权最小且唯一最小生成树不变, edge(x,y).weight 应是原树中 road(x,y).maxweight+1。

      关于如何统计所有边权和, 有一个很具有启发意义的算法:

      将原树的边按照边权从小到大的顺序依次加到图中, 每次加边都会联通两个联通块, 除了这条边以外, 所有连接这两个联通块的边都与树构成一个环, 环上的最大路径是本次加的边(注意这时边已经加上了), 统计即可。

      POJ1639

      求图的最小生成树, 满足 1 号节点的度数不超过 S。

      考虑最后加与 1 号节点相关的边:首先去掉 1 号节点, 对剩下的联通块分别求出最小生成树, 并试图用不超过 S 条边将图联通, 据定理, 这几条边应尽量短。最后尝试调整。

      算法的正确性:最初看到这个算法我也是懵逼的, 因为原文的叙述方法是有问题的, 应该先讲将图联通, 这样就好理解了。

      POJ2728 最优比率生成树

      0/1 分数规划问题, 待补。

      CH6202

      最短路+计数

      CH6B06

      最终答案一定是加了几条边, 构成几个联通块, 几个联通块内部点权和都是 0。

      显然如果固定几个点要形成联通块, 最小代价一定是这些点生成子图的最小生成树的边权之和。

      预处理出所有联通块, 做一个类似背包的算法吗可以保证最优解一定会这样的算法覆盖到。

      代码暂时不写了。


      树的重心

      定义 相比于删去其他节点,删去重心后的剩下的最大子树的值最小。

      性质

      0.删去重心后的所有子树节点数都不超过原树的 (dfrac{n}2)

      1.一棵树最多有两个重心, 且相邻。

      2.所有节点到重心的距离和最小, 对于两个重心, 相等。

      3.两个树用一条边连起来, 新的重心在连接原两棵树的重心的路径上。

      4.树添加或删除一个叶子节点, 重心最多只会移动到相邻的节点。

      找到一篇观感不错的博文, 是对树的重心的一部分性质的证明, 点 这里 可以看到。

      这里我也简单证明一下:(这里除法都是整除)

      由性质 0 推演出性质 1~4, 这个性质太强啦!

      0.朝子树节点数超过 (dfrac{n}2) 的子树走一步, 明显更可能成为重心。

      1.如果树上存在两个不相邻的重心 x,y, 它们往对方的方向各走一步, 成为 x^'^ 和 y^'^ , 由树的重心的性质 0 可知, (siz[x^{'}] + siz[y^{'}] le n), 然而此时(x,y不相邻)这两个东西加起来明显是 n+一个非零的数值, 所以矛盾了, 树上不存在两个不相邻的重心, 即所有重心要么相等要么互相相邻, 故不存在两个以上的重心, 然而确实有树有两个重心, 命题自然得证。

      2.由性质 0 ,将两个重心之间的边断开, 所得两个子树节点数相等, 由此只需证明所有节点到重心的距离和最小。这个也挺显然的, 因为非重心必有一个子树大小超过 (dfrac{n}2) , 所以朝这个超过的子树走, 距离和一定会变小, 也就是说非重心的距离和一定不是最小的。

      话说考虑接下来两条的时候我把 n 当成常数错了一遍。

      3.其实挺显然的, 具体设计个量算算, 把不在路径上的点排除就行了。

      4.原先的 (dfrac{n}2) 可能等于 (dfrac{n-1}2) , 这样加入节点后就变成 (dfrac{n+1}2) , 这两者之间相差 1, 重心不移动。

      如果 (dfrac{n}2) 就是 (dfrac{n}2) , 大于 (dfrac{n-1}2) , 那么加入节点后还是 (dfrac{n}2) , 重心可能移动, 但显而易见最多移动一次。

      SHOI2005树的双重心

      基于这样一个事实:

      对于固定的 x,y, min{d(v,x), d(v,y)} 取到 d(v,x) 还是 d(x,y) 是以树中的一条边为界的。

      得出一个乱搞做法:枚举为界的边断开, 每枚举一条边就在剩下的两颗树里分别算最小值然后加起来, 最后取最小值。

      显然题目要求的答案是 (ge) 这样求出来的答案的, 然而答案是否是这个呢?即, 这样求出来的最优的 x,y,分界边还是那个断边吗?

      我目前没有能力证明这个, 就只好写了, 网上题解中也是这个做法, 也没有证明, 草。

      这道题的代码部分还是挺值得一写的。 对于初学者来说

      s~sssss~ S^ss^

      [CSP-S]2019树的重心

      首先直接做很难求, 考虑 (sum_{a in S} a = sum_{a} a*a在S中的出现次数)这个经典式子, 统计每个点 x 会成为多少个重心。

      由于 x 和另一个子树的连线上是原树的重心, 所以把原树重心作为根, 统计有多少边,在删掉它们后(这条边靠近根的那个点与 x 之间的路径上要有根节点), x 会是重心。

      然后就很好统计了(吧


      树的直径

      性质

      0.到一个点距离最长的点是直径端点之一

      1.将两棵树用一条边连起来, 新的直径的端点一定是这两棵树原来的直径的端点。

      2.给一棵树接上一个叶子节点, 直径最多只会改变一个端点

      3.若一棵树存在多个直径, 则这些直径交于一点且这个点是这些直径的中点。

      树形 DP 求直径

      void dp(int x) {
      	vis[x] = 1;
      	for(int i=head[x]; i; i=next[i]) {
      		int y=ver[i], z=weight[i];
      		if(vis[y]) continue;
      		dp(y);
      		ans = max(ans,d[y]+z+d[x]);
      		d[x] = max(d[x],d[y]+z);
      	}
      }
      

      BFS 求直径

      // 没法抄, 不写了。
      

      APIO2010巡逻

      同时,为了不浪费资金,每天巡警车必须经过新建的道路正好一次。

      读到这里感觉有点生草。

      这道题没什么可说的, 唯一的小坑就是 “图不是个二维结构”。

    • 相关阅读:
      c#配置文件
      C#预处理指令
      C#面向对象详解
      231. Power of Two
      226. Invert Binary Tree
      C语言函数入参压栈顺序为什么是从右向左?
      对C++ 虚函数的理解
      悲观锁和乐观锁
      什么是索引
      CHAR 和VARCHAR的区别
    • 原文地址:https://www.cnblogs.com/tztqwq/p/13572674.html
    Copyright © 2011-2022 走看看