次短路径与次小生成树问题的简单解法
[次短路径]
次短路径可以看作是k短路径问题的一种特殊情况,求k短路径有Yen算法等较为复杂的方法,对于次短路径,可以有更为简易的方法。下面介绍一种求两个顶点之间次短路径的解法。
我们要对一个有向赋权图(无向图每条边可以看作两条相反的有向边)的顶点S到T之间求次短路径,首先应求出S的单源最短路径。遍历有向图,标记出可以在最短路径上的边,加入集合K。然后枚举删除集合K中每条边,求从S到T的最短路径,记录每次求出的路径长度值,其最小值就是次短路径的长度。
在这里我们以为次短路径长度可以等于最短路径长度,如果想等,也可以看作是从S到T有不止一条最短路径。如果我们规定求从S到T大于最短路径长度的次短路径,则答案就是每次删边后大于原最短路径的S到T的最短路径长度的最小值。
用Dijkstra+堆求单源最短路径,则每次求最短路径时间复杂度为O(Nlog(N+M) + M),所以总的时间复杂度为O(NM*log(N+M) + M^2)。该估计是较为悲观的,因为一般来说,在最短路径上的边的条数要远远小于M,所以实际效果要比预想的好。
[次小生成树]
类比上述次短路径求法,很容易想到一个“枚举删除最小生成树上的每条边,再求最小生成树”的直观解法。如果用Prim+堆,每次最小生成树时间复杂度为O(Nlog(N+M) + M),枚举删除有O(N)条边,时间复杂度就是O(N^2log(N+M) + N*M),当图很稠密时,接近O(N^3)。这种方法简易直观,但我们有一个更简单,而且效率更高的O(N^2+M)的解法,下面介绍这种方法。
首先求出原图最小生成树,记录权值之和为MinST。枚举添加每条不在最小生成树上的边(u,v),加上以后一定会形成一个环。找到环上权值第二大的边(即除了(u,v)以外的权值最大的边),把它删掉,计算当前生成树的权值之和。取所有枚举修改的生成树权值之和的最小值,就是次小生成树。
具体实现时,更简单的方法是从每个节点i遍历整个最小生成树,定义F[j]为从i到j的路径上最大边的权值。遍历图求出F[j]的值,然后对于添加每条不在最小生成树中的边(i,j),新的生成树权值之和就是MinST + w(i,j) - F[j],记录其最小值,则为次小生成树。
该算法的时间复杂度为O(N^2 + M)。由于只用求一次最小生成树,可以用最简单的Prim,时间复杂度为O(N^2)。算法的瓶颈不在求最小生成树,而在O(N^2+M)的枚举加边修改,所以用更好的最小生成树算法是没有必要的。
[次短路径与次小生成树的例题]
HAOI 2005 路由选择问题 直接求次短路径。
pku 3255 Roadblocks 稍微特殊的次短路径,允许边重复走。
Ural 1416 Confidential 求次小生成树的问题、
pku 1679 The Unique MST 判断最小生成树是否唯一。
[参考资料]
BYVoid 原创讲解,转载请注明。
HDU 4756 次小生成树
分类: MST
2013-09-23 20:56
81人阅读
收藏
举报
题意:给你n(n = 1000)个二维点,第一个点是power plant,还有n - 1个点是dormitories。然后现在知道有一条寝室到寝室的边是不能连的,但是我们不知道是哪条边,问这种情况下,最小生成树的最大值。
思路:有一种很朴素的算法就是枚举每一条被删除的边,然后做最小生成树,复杂度高的突破天际,显然不可以。
但是既然想到了上面的那种做法,再想一下就可以得到,我们还是枚举每一条被删除的边,只是这些边都是树边,所以关键就在于,删除这条边之后的图的最小生成树和原来的最小生成树的关系。
我们可以先预处理出来MINE[i][j], 表示点i 到点j 的最短的一条边。然后再预处理出来NMIN[i][j], 表示去掉边i -> j 之后,i -> j的最短边。
那么去掉一条边的增量就是NMIN[i][j] - MINE[i][j] 。然后取最大值即可。
- #include <set>
- #include <map>
- #include <stack>
- #include <cmath>
- #include <queue>
- #include <cstdio>
- #include <string>
- #include <vector>
- #include <iomanip>
- #include <cstring>
- #include <iostream>
- #include <algorithm>
- #define Max 2505
- #define FI first
- #define SE second
- #define ll long long
- #define PI acos(-1.0)
- #define inf 0x3fffffff
- #define LL(x) ( x << 1 )
- #define bug puts("here")
- #define PII pair<int,int>
- #define RR(x) ( x << 1 | 1 )
- #define mp(a,b) make_pair(a,b)
- #define mem(a,b) memset(a,b,sizeof(a))
- #define REP(i,s,t) for( int i = ( s ) ; i <= ( t ) ; ++ i )
-
- using namespace std;
-
- inline void RD(int &ret) {
- char c;
- int flag = 1 ;
- do {
- c = getchar();
- if(c == '-')flag = -1 ;
- } while(c < '0' || c > '9') ;
- ret = c - '0';
- while((c=getchar()) >= '0' && c <= '9')
- ret = ret * 10 + ( c - '0' );
- ret *= flag ;
- }
-
- #define N 1111
- #define M 111111
- int st[M] ;
- int top ;
- double NMIN[N][N] ;
- int x[N] , y[N] , cost , n ;
- double Map[N][N] ;
- double dis[N] ;
- bool vis[N] ;
- vector<int>G[N] ;
- priority_queue<kdq>qe ;
- double mst = 0 ;
- bool ismst[N][N] ;
- double MINE[N][N] ;
- double getdis(int i , int j){
- return sqrt(1.0 * (x[i] - x[j]) * (x[i] - x[j]) + 1.0 * (y[i] - y[j]) * (y[i] - y[j])) ;
- }
-
- struct kdq{
- int s , e ;
- double l ;
- bool operator < (const kdq & fk)const {
- return l > fk.l ;
- }
- } ;
-
- void Prim(){
- mst = 0 ;
- mem(ismst, 0) ;
- while(!qe.empty())qe.pop() ;
- for (int i = 0 ; i < n ; i ++ )dis[i] = Map[0][i] , vis[i] = 0 ,G[i].clear() ,qe.push((kdq){0 , i , dis[i]}) ;
- dis[0] = 0 , vis[0] = 1 ;
- while(!qe.empty()){
- kdq tp = qe.top() ; qe.pop() ;
- if(vis[tp.e])continue ;
- mst += Map[tp.s][tp.e] ;
- vis[tp.e] = 1 ;
- G[tp.s].push_back(tp.e) ; G[tp.e].push_back(tp.s) ;
- ismst[tp.e][tp.s] = ismst[tp.s][tp.e] = 1 ;
- MINE[tp.s][tp.e] = MINE[tp.e][tp.s] = inf ;
- for (int i = 0 ; i < n ; i ++ )if(!vis[i] && dis[i] > Map[tp.e][i])dis[i] = Map[tp.e][i] , qe.push((kdq){tp.e , i , dis[i]}) ;
- }
- }
-
- void dfs(int root , int fa , int now){
- int sz = G[now].size() ;
- for (int i = 0 ; i < sz ; i ++ ){
- int e = G[now][i] ;
- if(e != fa)dfs(root , now , e) , MINE[root][now] = min(MINE[root][now] , MINE[root][e]) ;
- }
- }
-
- void dfs(int fa, int now){
- int fk = top ;
- st[ ++ top] = now ;int sz = G[now].size() ;
- for (int i = 0 ; i < sz ; i ++ ){
- int e = G[now][i] ; if(e != fa)dfs(now , e) ;
- }
- if(fa != -1){
- NMIN[now][fa] = NMIN[fa][now] = inf ;
- for (int i = fk + 1 ; i <= top ; i ++ )NMIN[now][fa] = NMIN[fa][now] = min(NMIN[fa][now] , MINE[st[i]][fa]) ;
- }
- }
- void solve(){
- cin >> n >> cost ;
- for (int i = 0 ; i < n ; i ++ )RD(x[i]) , RD(y[i]) ;
- for (int i = 0 ; i < n ; i ++ )
- for (int j = 0 ; j < n ; j ++ )
- MINE[i][j] = Map[i][j] = (i == j) ? 0 : getdis(i , j) ;
- Prim() ;
- for (int i = 0 ; i < n ; i ++ )dfs(i , -1 , i ) ;
- top = 0 ;
- dfs(-1 , 0) ;
- double ans = 0 ;
- for (int i = 0 ; i < n ; i ++ ){
- for (int j = 0 ; j < n ; j ++ ){
- if(i == 0 || j == 0)continue ;
- if(!ismst[i][j])continue ;
- ans = max(ans , NMIN[i][j] - Map[i][j]) ;
- }
- }
- printf("%.2f\n",(ans + mst) * cost) ;
- }
- int main() {
- int _ ; cin >> _ ; while(_ --)solve() ;
- return 0 ;
- }
hdu 4756 Install Air Conditioning(2013南京网络赛)
分类: dp
2013-09-23 13:13
303人阅读
收藏
举报
题意就是求MST中删去一条边后,次小生成树最大的那颗。注意题中说了“ there are so many wires between two specific dormitories” 。也就是说与0节点(power plant)相连的MST边是不会出故障的,所以要特殊考虑一下所有MST边都跟0节点相连的情况。
删除MST中某条边后的次小生成树用树形dp解决,传送门
我的理解是:删除MST边<u, v>之后, MST被划分成u跟v两个子树,新添加的非MST边必然是连接u跟v两颗子树的原图中的非MST边,这可以用dfs解决。dp[u][v]代表两颗子树的最短距离,用每个点p的非MST边g[p][i]去更新所有的dp[u][v],时间复杂度O(n^2)。
-
- #include<algorithm>
- #include<iostream>
- #include<cstring>
- #include<fstream>
- #include<sstream>
- #include<vector>
- #include<string>
- #include<cstdio>
- #include<bitset>
- #include<queue>
- #include<stack>
- #include<cmath>
- #include<map>
- #include<set>
- #define FF(i, a, b) for(int i=a; i<b; i++)
- #define FD(i, a, b) for(int i=a; i>=b; i--)
- #define REP(i, n) for(int i=0; i<n; i++)
- #define CLR(a, b) memset(a, b, sizeof(a))
- #define debug puts("**debug**")
- #define LL long long
- #define PB push_back
- #define MP make_pair
- #define eps 1e-10
- using namespace std;
-
- const int maxn = 1010;
- const double INF = 1e20;
- int n, T, fa[maxn];
- double mst, k, x[maxn], y[maxn], g[maxn][maxn], dp[maxn][maxn], d[maxn];
- bool vis[maxn];
- vector<int> G[maxn];
-
- template <class T> T sqr(T x) { return x*x; }
- double dist(int i, int j)
- {
- return sqrt(sqr(x[i]-x[j]) + sqr(y[i] - y[j]));
- }
-
- void read()
- {
- scanf("%d%lf", &n, &k);
- REP(i, n) scanf("%lf%lf", &x[i], &y[i]);
- REP(i, n)
- {
- FF(j, i+1, n) g[i][j] = g[j][i] = dist(i, j), dp[i][j] = dp[j][i] = INF;
- g[i][i] = INF;
- vis[i] = 0;
- fa[i] = 0;
- G[i].clear();
- }
- }
-
- void prim()
- {
- REP(i, n) d[i] = g[0][i];
- vis[0] = 1;
- fa[0] = -1;
- d[0] = INF;
- mst = 0;
- FF(i, 1, n)
- {
- int pos = 0;
- FF(j, 1, n) if(!vis[j] && d[pos] > d[j]) pos = j;
-
- mst += d[pos];
- vis[pos] = 1;
-
-
- G[pos].PB(fa[pos]);
- G[fa[pos]].PB(pos);
-
- FF(j, 1, n) if(!vis[j] && g[pos][j] < d[j])
- d[j] = g[pos][j], fa[j] = pos;
- }
- }
-
-
- double dfs(int p, int u, int f)
- {
- double ans = INF;
- REP(i, G[u].size())
- {
- int v = G[u][i];
- if(v != f)
- {
- double tmp = dfs(p, v, u);
- ans = min(ans, tmp);
- dp[u][v] = dp[v][u] = min(dp[u][v], tmp);
- }
- }
-
- if(p != f) ans = min(ans, g[p][u]);
- return ans;
- }
-
- double solve()
- {
- REP(i, n) dfs(i, i, -1);
-
-
- bool flag = 0;
- FF(i, 1, n) if(fa[0] != i && fa[i] != 0) flag = 1;
- if(!flag) return mst * k;
-
- double ret = 0;
-
- FF(i, 1, n) FF(j, i+1, n) if(fa[i] == j || fa[j] == i)ret = max(ret, dp[i][j] - g[i][j]);
- return (mst + ret) * k;
- }
-
- int main()
- {
- scanf("%d", &T);
- while(T--)
- {
- read();
- prim();
- printf("%.2lf\n", solve());
- }
- return 0;
- }
hdu 4756 Install Air Conditioning(2013南京网络赛)
分类: dp
2013-09-23 13:13
303人阅读
收藏
举报
题意就是求MST中删去一条边后,次小生成树最大的那颗。注意题中说了“ there are so many wires between two specific dormitories” 。也就是说与0节点(power plant)相连的MST边是不会出故障的,所以要特殊考虑一下所有MST边都跟0节点相连的情况。
删除MST中某条边后的次小生成树用树形dp解决,传送门
我的理解是:删除MST边<u, v>之后, MST被划分成u跟v两个子树,新添加的非MST边必然是连接u跟v两颗子树的原图中的非MST边,这可以用dfs解决。dp[u][v]代表两颗子树的最短距离,用每个点p的非MST边g[p][i]去更新所有的dp[u][v],时间复杂度O(n^2)。
-
- #include<algorithm>
- #include<iostream>
- #include<cstring>
- #include<fstream>
- #include<sstream>
- #include<vector>
- #include<string>
- #include<cstdio>
- #include<bitset>
- #include<queue>
- #include<stack>
- #include<cmath>
- #include<map>
- #include<set>
- #define FF(i, a, b) for(int i=a; i<b; i++)
- #define FD(i, a, b) for(int i=a; i>=b; i--)
- #define REP(i, n) for(int i=0; i<n; i++)
- #define CLR(a, b) memset(a, b, sizeof(a))
- #define debug puts("**debug**")
- #define LL long long
- #define PB push_back
- #define MP make_pair
- #define eps 1e-10
- using namespace std;
-
- const int maxn = 1010;
- const double INF = 1e20;
- int n, T, fa[maxn];
- double mst, k, x[maxn], y[maxn], g[maxn][maxn], dp[maxn][maxn], d[maxn];
- bool vis[maxn];
- vector<int> G[maxn];
-
- template <class T> T sqr(T x) { return x*x; }
- double dist(int i, int j)
- {
- return sqrt(sqr(x[i]-x[j]) + sqr(y[i] - y[j]));
- }
-
- void read()
- {
- scanf("%d%lf", &n, &k);
- REP(i, n) scanf("%lf%lf", &x[i], &y[i]);
- REP(i, n)
- {
- FF(j, i+1, n) g[i][j] = g[j][i] = dist(i, j), dp[i][j] = dp[j][i] = INF;
- g[i][i] = INF;
- vis[i] = 0;
- fa[i] = 0;
- G[i].clear();
- }
- }
-
- void prim()
- {
- REP(i, n) d[i] = g[0][i];
- vis[0] = 1;
- fa[0] = -1;
- d[0] = INF;
- mst = 0;
- FF(i, 1, n)
- {
- int pos = 0;
- FF(j, 1, n) if(!vis[j] && d[pos] > d[j]) pos = j;
-
- mst += d[pos];
- vis[pos] = 1;
-
-
- G[pos].PB(fa[pos]);
- G[fa[pos]].PB(pos);
-
- FF(j, 1, n) if(!vis[j] && g[pos][j] < d[j])
- d[j] = g[pos][j], fa[j] = pos;
- }
- }
-
-
- double dfs(int p, int u, int f)
- {
- double ans = INF;
- REP(i, G[u].size())
- {
- int v = G[u][i];
- if(v != f)
- {
- double tmp = dfs(p, v, u);
- ans = min(ans, tmp);
- dp[u][v] = dp[v][u] = min(dp[u][v], tmp);
- }
- }
-
- if(p != f) ans = min(ans, g[p][u]);
- return ans;
- }
-
- double solve()
- {
- REP(i, n) dfs(i, i, -1);
-
-
- bool flag = 0;
- FF(i, 1, n) if(fa[0] != i && fa[i] != 0) flag = 1;
- if(!flag) return mst * k;
-
- double ret = 0;
-
- FF(i, 1, n) FF(j, i+1, n) if(fa[i] == j || fa[j] == i)ret = max(ret, dp[i][j] - g[i][j]);
- return (mst + ret) * k;
- }
-
- int main()
- {
- scanf("%d", &T);
- while(T--)
- {
- read();
- prim();
- printf("%.2lf\n", solve());
- }
- return 0;
- }
图论专题训练1-C(最短路和次短路问题,dijkstra算法)
分类: 算法题解-图论 算法题解-图论-最短路径
2013-09-26 13:05
109人阅读
收藏
举报
ACM 算法 dijkstra
题目链接
-
-
-
-
-
-
-
-
-
-
-
-
-
- #include<iostream>
- #include<cstdio>
- #include<cstring>
- #include<string>
- #include<algorithm>
- using namespace std;
-
- const int N=11111;
- const int M=111111;
- const int INF=0xffffff;
-
- struct node
- {
- int to;
- int w;
- int next;
- };
-
- node edge[N];
- int head[N];
-
- int dist[N][2],cnt[N][2];
- bool vis[N][2];
- int n,m,s,t,edges;
-
- void addedge(int u,int v,int w)
- {
- edge[edges].w=w;
- edge[edges].to=v;
- edge[edges].next=head[u];
- head[u]=edges++;
- }
-
- int dijkstra()
- {
- int k;
- for(int i=0; i<=n; i++)
- {
- dist[i][0]=dist[i][1]=INF;
- vis[i][0]=vis[i][1]=0;
- cnt[i][0]=cnt[i][1]=0;
- }
- cnt[s][0]=1,dist[s][0]=0;
-
- for(int i=1; i<=n*2; i++)
- {
- int u=-1;
- int min_dist=INF;
- for(int j=1; j<=n; j++)
- for(int flag=0; flag<2; flag++)
- if(!vis[j][flag]&&dist[j][flag]<min_dist)
- {
- min_dist=dist[j][flag];
- u=j;
- k=flag;
- }
- if(u==-1)
- break;
- vis[u][k]=true;
- for(int e=head[u]; e!=-1; e=edge[e].next)
- {
- int j=edge[e].to;
- int tmp=dist[u][k]+edge[e].w;
-
- if(tmp<dist[j][0])
- {
- dist[j][1]=dist[j][0];
- cnt[j][1]=cnt[j][0];
- dist[j][0]=tmp;
- cnt[j][0]=cnt[u][k];
- }
-
- else if(tmp==dist[j][0])
- {
- cnt[j][0]+=cnt[u][k];
- }
-
- else if(tmp<dist[j][1])
- {
- dist[j][1]=tmp;
- cnt[j][1]=cnt[u][k];
- }
-
- else if(tmp==dist[j][1])
- {
- cnt[j][1]+=cnt[u][k];
- }
- }
- }
-
- int res=cnt[t][0];
- if(dist[t][0]+1==dist[t][1])
- res+=cnt[t][1];
- return res;
- }
-
- int main()
- {
-
- int tcase;
- scanf("%d",&tcase);
- while(tcase--)
- {
- edges=0;
- scanf("%d%d",&n,&m);
- memset(head,-1,sizeof(head));
- int u,v,w;
- for(int i=0; i<m; i++)
- {
- scanf("%d%d%d",&u,&v,&w);
- addedge(u,v,w);
- }
- scanf("%d%d",&s,&t);
- printf("%d\n",dijkstra());
- }
- return 0;
- }