最小树形图

好像是一个OI中应用不是很多(不要打脸)的算法

算法

朱刘算法了解一下本文就是讲这个的......

其实主要是我调了很久一道题,然后发现一个智障错误然后过来写博客

算法主要解决有向图最小生成树问题.

定义

在一个有向图中选择一些有向边集构建一颗树,然后使这棵树的边权和最小。

就是根确定的“最小有向生成树”。

前面的东西

Q:如何判断存在性?

A:直接拿着根跑dfs即可。

Q:复杂度?

A:$O(VE)$

我们认为根$Root$一开始已经给定(后面会将没给定的特殊情况)。

扫描二维码关注公众号,回复: 1459950 查看本文章

记边权为$w$

一下的证明不一定会严谨......

算法

 首先我们需要清除自环,因为存在自环的话会使复杂度增高但又没有意义。

操作一

  对于除根以外的所有点选定一条入边,该条入边是所有入边中权值最小的一条。

定理一

  上面的操作之后如果没有环的话那么就是最小树形图。

  证明:

  如果不是最小树形图的话那么一定存在一条边可以替代原有的边,但是由于原图已经是一棵树,所以替换一条边只可能:

  1. 破坏树的结构
  2. 换到了一条更大的边

  所以并没有可以换的边。

然后如果没有环的话那么就直接跑路输出答案(等下介绍缩了环之后的答案怎么计算),如果有呢?

如果存在一个环的话,那么我们这样做缩环:

操作二

  这一操作命名为缩环(自己瞎BB的)

  令环上任意一点$u$,指向它的有向边为$v$点,令$u$和$v$之间的边的边权为$ind[u]$,然后我们新建了一个节点$p$代替这个环,并且与外界有如下联系:

  对于点$u$的入边(起点为$s$)连接$s$到$p$,边权为$w-ind[u]$

  对于点$u$的出边(起点为$t$)连接$p$到$t$,边权为$w$

  例如下面这幅比较原谅的图,假设所有绿色的点为新点$p$所代表的点。左上角的连边说明了一个实例。

定理二

  对于上面的操作,当前这一层的最小树形图=缩环后的最小树形图+环内权值

  证明(解释为什么要在缩环的时候换边权):

  由于生成树要求每一个点都要走到,包括环内的点,所以最后的答案肯定长成这张图的样子,即$x$条路径(图中的红色边和蓝色路径,$x=2$)覆盖了这个环(没有考虑这种事会不会发生,反正不会发生那就只有一条路径更易证)。我们发现环内的$x$条边肯定是走不到的,也就是图中的黄色边,也就是$v$到$u$的边,边权为$ind[u]$。也就是说如果有一条路径从$u$进入了这个环,那么环内$v$到$u$的边就会不在生成树中。

  所以我们只需要在入边的$w$上减去$ind[u]$即可保证这条的排名以及在最终对答案产生的贡献正确。

 

最后我们只需要递归地跑算法即可。

复杂度组成

  每次找最小边$O(E)$,缩点$O(V)$,更新边$O(E)$,由于删掉了自环,所有每次至少删掉一个点,所以递归层数$O(V)$

  总复杂度$O(VE)$

后面的东西

  如果我这个虾皮出题人没有指定根怎么办?让你去枚举根?

  那么我们可以建立虚拟根$root$(小写),然后向每个点连出$S>\Sigma{W_i}$的边,然后再跑,最后答案减去S即可。

  但是有一种情况就是发现减了之后答案还是大于$S$,那么这说明原图不连通(因为如果连通的话肯定算法不会智障到去选边权为S边)

  然后我们找到的最小边的和$root$相连的点就是最小根。

例题

Luogu P2792 [JSOI2008]小店购物

题意

就是去交易买东西,一些货物有原价,如果你先买$x$货物然后再买$y$货物搞不好就有优惠)。

你需要为每一种物品不多不少选$k_i$件。

题解

考虑到如果我们选择最优方案,那么只有第一个物品是需要考虑折扣的,后面的物品可以直接选取最便宜的。

首先统计后面优惠价的所有答案。

我们为每一个物品都开一个节点,然后再开一个$root$, 从$root$向物品连边,权值为原价

然后对于每一对优惠$x$对$y$,从$x$向$y$连边,边权为折扣价。

最后跑一边算法然后累加到原来的答案内即可。

一开始我的板子出了一个非常......的错误,然后调了几年,然后又被卡精度......

代码如下:

  1 #include <cstdio>
  2 #include <cctype>
  3 #include <cassert>
  4 #include <cstring>
  5 
  6 #include <fcntl.h>
  7 #include <unistd.h>
  8 #include <sys/mman.h>
  9 
 10 //User's Lib
 11 
 12 using namespace std;
 13 
 14 char *pc;
 15 
 16 inline void Main_Init(){
 17     static bool inited = false;
 18     if(inited) fclose(stdin), fclose(stdout);
 19     else {
 20         #ifndef ONLINE_JUDGE
 21         freopen("b.in", "r", stdin);
 22         freopen("b.out", "w", stdout);
 23         #endif
 24         pc = (char *) mmap(NULL, lseek(0, 0, SEEK_END), PROT_READ, MAP_PRIVATE, 0, 0);
 25         inited = true;
 26     }
 27 }
 28 
 29 static inline int read(){
 30     int num = 0;
 31     char c, sf = 1;
 32     while(isspace(c = *pc++));
 33     if(c == 45) sf = -1, c = *pc ++;
 34     while(num = num * 10 + c - 48, isdigit(c = *pc++));
 35     return num * sf;
 36 }
 37 
 38 static inline double read_dec(){
 39     double num = 0, decs = 1;
 40     char c, sf = 1;
 41     while(isspace(c = *pc ++));
 42     if(c == '-') sf = -1, c = *pc ++;
 43     while(num = num * 10 + c - 48, isdigit(c = *pc ++));
 44     if(c != '.') return num * sf;
 45     c = *pc ++;
 46     while(num += (decs *= 0.1) * (c - 48), isdigit(c = *pc ++));
 47     return num * sf;
 48 }
 49 
 50 namespace LKF{
 51     template <typename T>
 52     extern inline T abs(T tar){
 53         return tar < 0 ? -tar : tar;
 54     }
 55     template <typename T>
 56     extern inline void swap(T &a, T &b){
 57         T t = a;
 58         a = b;
 59         b = t;
 60     }
 61     template <typename T>
 62     extern inline void upmax(T &x, const T &y){
 63         if(x < y) x = y;
 64     }
 65     template <typename T>
 66     extern inline void upmin(T &x, const T &y){
 67         if(x > y) x = y;
 68     }
 69     template <typename T>
 70     extern inline T max(T a, T b){
 71         return a > b ? a : b;
 72     }
 73     template <typename T>
 74     extern inline T min(T a, T b){
 75         return a < b ? a : b;
 76     }
 77 }
 78 
 79 //Source Code
 80 
 81 const int MAXN = 111;
 82 const int MAXM = 555;
 83 const int INF = 0x3f3f3f3f;
 84 
 85 int n, m;
 86 int ind[MAXN], pre[MAXN], id[MAXN], vis[MAXN];
 87 struct Edge{
 88     int u, v, w;
 89     Edge(){}
 90     Edge(int _u, int _v, int _w) : u(_u), v(_v), w(_w){}
 91 }edge[MAXM];
 92 
 93 inline int MA(){
 94     int ret = 0, root = n, num;
 95     while(true){
 96         memset(ind, 0x3f, sizeof(ind));
 97         for(int i = 1; i <= m; i++)
 98             if(edge[i].u != edge[i].v && ind[edge[i].v] > edge[i].w)
 99                 ind[edge[i].v] = edge[i].w, pre[edge[i].v] = edge[i].u;
100         for(int i = 1; i <= n; i++)
101             if(i != root && ind[i] == INF)
102                 return -1;
103         memset(id, -1, sizeof(id)), memset(vis, -1, sizeof(vis));
104         num = ind[root] = 0;
105         for(int i = 1; i <= n; i++){
106             int v = i;
107             ret += ind[i];
108             while(vis[v] != i && v != root)
109                 vis[v] = i, v = pre[v];
110             if(v != root && id[v] == -1){
111                 id[v] = ++ num;
112                 for(int j = pre[v]; j != v; j = pre[j])
113                     id[j] = num;
114             }
115         }
116         if(!num) return ret;
117         for(int i = 1; i <= n; i++)
118             if(id[i] == -1)
119                 id[i] = ++ num;
120         for(int i = 1; i <= m; i++){
121             int ori = edge[i].v;//ori
122             edge[i].u = id[edge[i].u], edge[i].v = id[edge[i].v];
123             if(edge[i].u != edge[i].v) edge[i].w -= ind[ori];
124         }
125         n = num, root = id[root];
126     }
127 }
128 
129 int cost[MAXN], ks[MAXN], pos[MAXN];
130 
131 inline void Re(int &tar){
132     if(tar % 10 != 0){
133         tar ++;
134         //printf("%d\n", tar);
135     }
136 }
137 
138 int main(){
139     Main_Init();
140     n = read();
141     for(int i = 1, j = 1; i <= n; i++, j++){
142         Re(cost[i] = double(read_dec()) * 100.0), ks[i] = read();
143         if(!ks[i]) i --, n --;
144         else ks[i] --,  pos[j] = i;
145     }
146     n ++;
147     for(int i = 1; i < n; i++)
148         edge[++ m] = Edge(n, i, cost[i]);
149     int t = read();
150     for(int i = 1; i <= t; i++){
151         int x = pos[read()], y = pos[read()], w;
152         Re(w = double(read_dec()) * 100.0);
153         if(!(x && y)) continue;
154         LKF::upmin(cost[y], w);
155         edge[++ m] = Edge(x, y, w);
156     }
157     int ans = 0;
158     for(int i = 1; i < n; i++)
159         ans += cost[i] * ks[i];
160     int ret = MA();
161     assert(ret != -1);
162     printf("%.2lf", (ret + ans) / 100.0);
163     Main_Init();
164     return 0;
165 }
Source Code

参考文献|资料:

我也找不到原论文,算法是由朱永津与刘振宏提出的,对此表示敬意。

图是自己画的......

猜你喜欢

转载自www.cnblogs.com/CreeperLKF/p/9136097.html
今日推荐