为了表明我还活着

目录


    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巡逻

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

    读到这里感觉有点生草。

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

    原文地址:https://www.cnblogs.com/tztqwq/p/13572674.html