因为最近测试遇到了分层图的题目,所以稍微学了一下==。


这种题目一般是来解决最短路边权有变化/有k条免费路的问题的。他们基本都一般有两种实现方式:dp+最短路/分层图+最短路

当然你如果非要说他们是一样的我也没Fa♂反驳qwq


一、dp+最短路(以dij为例)

我们一般的球最短路都是在一维上进行的。设$dis[k]$为从起点到$k$的最短路。但是如果多了条件,比如有$k$条道路可以选择边权变化,那么就可以使用这种方法。

P4822 [BJWC2012]冻结 这道题为例,它给出的条件是有$k$条路可以把原来的边权变为一半,那么我们就可以设$dis[k][h]$为到达$k$点,已经在$h$条路上把原来的边权变为一半的最短路。这是不是很像dp的状态的呀?是不是呀?

那么在我们平时松弛的时候,就可以看做dp的转移了。这里有两种:使用“改边卡”和不使用“改边卡”。

其他就与普通dij无异了。

当然最后的答案需要在使用0~k张改边卡间取最小值。

  1. #include<cstdio>
  2. #include<algorithm>
  3. #include<queue>
  4. #include<cstring>
  5.  
  6. using namespace std;
  7.  
  8. int n,m,k,tot,ans=;
  9. int head[],dis[][],vis[][];
  10. struct node{
  11. int to,next,val;
  12. }edge[];
  13. struct cellur{
  14. int dis,p,cnt;
  15. };
  16.  
  17. bool operator < (const cellur &a,const cellur &b)
  18. {
  19. return a.dis>b.dis;
  20. }
  21.  
  22. void add(int x,int y,int z)
  23. {
  24. edge[++tot].to=y;
  25. edge[tot].next=head[x];
  26. head[x]=tot;
  27. edge[tot].val=z;
  28. }
  29.  
  30. void dijkstra()
  31. {
  32. memset(dis,0x3f,sizeof(dis));
  33. priority_queue<cellur>q;
  34. dis[][]=;q.push((cellur){,,});
  35. while(!q.empty())
  36. {
  37. int u=q.top().p;
  38. int num=q.top().cnt;q.pop();
  39. if(vis[u][num]) continue;
  40. vis[u][num]=;
  41. for(int i=head[u];i;i=edge[i].next)
  42. {
  43. int v=edge[i].to;
  44. if(num<k&&dis[v][num+]>dis[u][num]+edge[i].val/)
  45. {
  46. dis[v][num+]=dis[u][num]+edge[i].val/;
  47. q.push((cellur){dis[v][num+],v,num+});
  48. }
  49. if(dis[v][num]>dis[u][num]+edge[i].val)
  50. {
  51. dis[v][num]=dis[u][num]+edge[i].val;
  52. q.push((cellur){dis[v][num],v,num});
  53. }
  54. }
  55. }
  56. }
  57.  
  58. int main()
  59. {
  60. scanf("%d%d%d",&n,&m,&k);
  61. for(int i=;i<=m;i++)
  62. {
  63. int x=,y=,z=;
  64. scanf("%d%d%d",&x,&y,&z);
  65. add(x,y,z),add(y,x,z);
  66. }
  67. dijkstra();
  68. for(int i=;i<=k;i++)
  69. ans=min(ans,dis[n][i]);
  70. printf("%d",ans);
  71. return ;
  72. }

P4568 [JLOI2011]飞行路线 这是一个同样的题目==。

  1. #include<cstdio>
  2. #include<algorithm>
  3. #include<cstring>
  4. #include<queue>
  5.  
  6. using namespace std;
  7.  
  8. int n,m,k,s,t,tot,ans=;
  9. int head[];
  10. int dis[][];
  11. bool vis[][];
  12. struct node{
  13. int to,next,val;
  14. }edge[];
  15. struct cellur{
  16. int dis,p,cnt;
  17. };
  18. bool operator < (const cellur &a,const cellur &b)
  19. {
  20. return a.dis>b.dis;
  21. }
  22.  
  23. void add(int x,int y,int z)
  24. {
  25. edge[++tot].to=y;
  26. edge[tot].next=head[x];
  27. head[x]=tot;
  28. edge[tot].val=z;
  29. }
  30.  
  31. void dijkstra()
  32. {
  33. memset(dis,0x3f,sizeof(dis));
  34. priority_queue<cellur>q;
  35. dis[s][]=;
  36. q.push((cellur){,s,});
  37. while(!q.empty())
  38. {
  39. int u=q.top().p;
  40. int num=q.top().cnt;q.pop();
  41. if(vis[u][num]) continue;
  42. vis[u][num]=;
  43. for(int i=head[u];i;i=edge[i].next)
  44. {
  45. int v=edge[i].to;
  46. if(num<k&&dis[v][num+]>dis[u][num])
  47. {
  48. dis[v][num+]=dis[u][num];
  49. q.push((cellur){dis[v][num+],v,num+});
  50. }
  51. if(dis[v][num]>dis[u][num]+edge[i].val)
  52. {
  53. dis[v][num]=dis[u][num]+edge[i].val;
  54. q.push((cellur){dis[v][num],v,num});
  55. }
  56. }
  57. }
  58. }
  59.  
  60. int main()
  61. {
  62. scanf("%d%d%d",&n,&m,&k);
  63. scanf("%d%d",&s,&t);s++;t++;
  64. for(int i=;i<=m;i++)
  65. {
  66. int x=,y=,z=;
  67. scanf("%d%d%d",&x,&y,&z);
  68. x++;y++;
  69. add(x,y,z);add(y,x,z);
  70. }
  71. dijkstra();
  72. for(int i=;i<=k;i++)
  73. ans=min(ans,dis[t][i]);
  74. printf("%d",ans);
  75. return ;
  76. }

二、分层图最短路

这 是什么?其实给他总结成一句话:就是拆点。

我们知道dp是遍历所有的状态的,那么如果我们在开始建边的时候就考虑将所有的状态都连上边,那么之后跑裸的最短路就行了。

如何建边?我们以P2939 [USACO09FEB]改造路Revamping Trails 这道题为例。(其实这三道题基本一样,只是这道题用拆点方法写了)

题目也是要求我们有$k$条边可以把它的边权变为0。那么我们可以把每个点拆成$k+1$个点,从0到$k$,第$i$个表示到这里要用$i$次改边卡。

可以参考这段代码感性理解下。

  1. for(int i=;i<=m;i++)
  2. {
  3. int x=,y=,z=;
  4. scanf("%d%d%d",&x,&y,&z);
  5. for(int j=;j<=k;j++)
  6. {
  7. add(x+j*n,y+j*n,z);add(y+j*n,x+j*n,z);//同层之间正常连边
  8. if(j!=k)
  9. add(x+j*n,y+(j+)*n,),add(y+j*n,x+(j+)*n,);//边权有变化了
  10. }
  11. }

之后跑一遍裸的dij后,我们同理在终点的所有可能状态中遍历取最小值。

  1. for(int i=;i<=k;i++)
  2. ans=min(ans,dis[n+i*n]);

个人认为这种方法的缺点是很难控制空间的开销。因为点数增加了$k+1$倍,边数也增加了很多倍。容易出现RE/MLE的情况。

  1. #include<cstdio>
  2. #include<algorithm>
  3. #include<cstring>
  4. #include<queue>
  5.  
  6. using namespace std;
  7.  
  8. int n,m,k,tot,ans=;
  9. int head[],dis[],vis[];
  10. struct node{
  11. int to,next,val;
  12. }edge[];
  13.  
  14. void add(int x,int y,int z)
  15. {
  16. edge[++tot].to=y;
  17. edge[tot].next=head[x];
  18. head[x]=tot;
  19. edge[tot].val=z;
  20. }
  21.  
  22. void dijkstra()
  23. {
  24. priority_queue<pair<int,int> >q;
  25. memset(dis,0x3f,sizeof(dis));
  26. dis[]=;q.push(make_pair(,));
  27. while(!q.empty())
  28. {
  29. int u=q.top().second;q.pop();
  30. if(vis[u]) continue;
  31. vis[u]=;
  32. for(int i=head[u];i;i=edge[i].next)
  33. {
  34. int v=edge[i].to;
  35. if(dis[v]>dis[u]+edge[i].val)
  36. {
  37. dis[v]=dis[u]+edge[i].val;
  38. q.push(make_pair(-dis[v],v));
  39. }
  40. }
  41. }
  42. }
  43.  
  44. int main()
  45. {
  46. scanf("%d%d%d",&n,&m,&k);
  47. for(int i=;i<=m;i++)
  48. {
  49. int x=,y=,z=;
  50. scanf("%d%d%d",&x,&y,&z);
  51. for(int j=;j<=k;j++)
  52. {
  53. add(x+j*n,y+j*n,z);add(y+j*n,x+j*n,z);
  54. if(j!=k)
  55. add(x+j*n,y+(j+)*n,),add(y+j*n,x+(j+)*n,);
  56. }
  57. }
  58. dijkstra();
  59. for(int i=;i<=k;i++)
  60. ans=min(ans,dis[n+i*n]);
  61. printf("%d",ans);
  62. return ;
  63. }

Over?

我们看一道不是那么套路的题吧!

给你一个无向图,求从1到n经过的边的边权绝对值之和最小的路径。而每经过一条边,这条边的边权就会改变。原边权为x,那么新边权就会变成1/1-x。

关于1/1-x这个式子,其实他是很有规律的,在进行三次迭代之后,它会回到原值。

举个栗子。设x=2,此函数为$f(x)$。那么

$f(1)=-1$

$f(-1)=1/2$

$f(1/2)=2$。是不是很神奇鸭?

那么我们可以仿照之前的方法,拆点。把一个点拆成三种状态,每个状态就是到它的那条边是它本身的第几种边权。

  1. for(int i=;i<=n;i++)
  2. for(int j=;j<=;j++)
  3. id[i][j]=++cnt;
  4. for(int i=;i<=m;i++)
  5. {
  6. int x=,y=;
  7. double z=;
  8. scanf("%d%d%lf",&x,&y,&z);
  9. add(id[x][],id[y][],z);add(id[y][],id[x][],z);
  10. z=1.0/(-z);double tmp=fabs(z);
  11. add(id[x][],id[y][],tmp);add(id[y][],id[x][],tmp);
  12. z=1.0/(-z);tmp=fabs(z);
  13. add(id[x][],id[y][],tmp);add(id[y][],id[x][],tmp);
  14. }

然后再跑dij就行惹。

  1. #include<cstdio>
  2. #include<algorithm>
  3. #include<queue>
  4. #include<cstring>
  5. #include<cmath>
  6. #define maxn 100090
  7. #define maxm 300090
  8.  
  9. using namespace std;
  10. const int inf=0x3f3f3f3f;
  11.  
  12. int n,m,tot,cnt;
  13. int id[maxn][],head[maxn*];
  14. bool vis[maxn*];
  15. double ans,dis[maxn*];
  16. struct node{
  17. int to,next;
  18. double val;
  19. }edge[maxm*];
  20.  
  21. void add(int x,int y,double z)
  22. {
  23. edge[++tot].to=y;
  24. edge[tot].next=head[x];
  25. head[x]=tot;
  26. edge[tot].val=z;
  27. }
  28.  
  29. void dijkstra()
  30. {
  31. priority_queue<pair<double,int> >q;
  32. for(int i=;i<=cnt;i++) dis[i]=inf;
  33. q.push(make_pair(,id[][]));
  34. dis[id[][]]=;
  35. while(!q.empty())
  36. {
  37. int x=q.top().second;q.pop();
  38. if(vis[x]) continue;
  39. vis[x]=;
  40. for(int i=head[x];i;i=edge[i].next)
  41. {
  42. int y=edge[i].to;
  43. if(dis[y]>dis[x]+edge[i].val)
  44. {
  45. dis[y]=dis[x]+edge[i].val;
  46. q.push(make_pair(-dis[y],y));
  47. }
  48. }
  49. }
  50. }
  51.  
  52. int main()
  53. {
  54. scanf("%d%d",&n,&m);
  55. for(int i=;i<=n;i++)
  56. for(int j=;j<=;j++)
  57. id[i][j]=++cnt;
  58. for(int i=;i<=m;i++)
  59. {
  60. int x=,y=;
  61. double z=;
  62. scanf("%d%d%lf",&x,&y,&z);
  63. add(id[x][],id[y][],z);add(id[y][],id[x][],z);
  64. z=1.0/(-z);double tmp=fabs(z);
  65. add(id[x][],id[y][],tmp);add(id[y][],id[x][],tmp);
  66. z=1.0/(-z);tmp=fabs(z);
  67. add(id[x][],id[y][],tmp);add(id[y][],id[x][],tmp);
  68. }
  69. dijkstra();
  70. ans=min(dis[id[n][]],min(dis[id[n][]],dis[id[n][]]));
  71. printf("%.3lf\n",ans);
  72. return ;
  73. }

感觉这种题还是比较套路的,只要发现是分层图,建边或跑dp都不难想的qwq。还有更多拓展的题目,给自己再留下一个天坑。

(光速逃)

分层图初探 By cellur925的更多相关文章

  1. 【BZOJ-3627】路径规划 分层图 + Dijkstra + spfa

    3627: [JLOI2014]路径规划 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 186  Solved: 70[Submit][Status] ...

  2. ACdream 1017 [分层图][网络流]

    /* 大连热身C题 不要低头,不要放弃,不要气馁,不要慌张 题意: 给一个城市路线图,给定起点给定终点.有n个货物从起点运送到终点.城市的边是无向边. 每个货物每天如果通过某条路,那么这天这条路只能运 ...

  3. poj3635Full Tank?[分层图最短路]

    Full Tank? Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 7248   Accepted: 2338 Descri ...

  4. HDU 5669 线段树优化建图+分层图最短路

    用线段树维护建图,即把用线段树把每个区间都标号了,Tree1中子节点有到达父节点的单向边,Tree2中父节点有到达子节点的单向边. 每次将源插入Tree1,汇插入Tree2,中间用临时节点相连.那么T ...

  5. BZOJ 2763 分层图最短路

    突然发现我不会分层图最短路,写一发. 就是同层中用双向边相连,用单向边连下一层 #include <cstdio> #include <algorithm> #include ...

  6. ZOJ-2364 Data Transmission 分层图阻塞流 Dinic+贪心预流

    题意:给定一个分层图,即只能够在相邻层次之间流动,给定了各个顶点的层次.要求输出一个阻塞流. 分析:该题直接Dinic求最大流TLE了,网上说采用Isap也TLE,而最大流中的最高标号预流推进(HLP ...

  7. 【网络流24题】 No.15 汽车加油行驶问题 (分层图最短路i)

    [题意] 问题描述:给定一个 N*N 的方形网格,设其左上角为起点◎, 坐标为( 1, 1), X 轴向右为正, Y轴向下为正, 每个方格边长为 1, 如图所示. 一辆汽车从起点◎出发驶向右下角终点▲ ...

  8. 【网络流24题】 No.14 孤岛营救问题 (分层图最短路)

    [题意] 1944 年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛, 营救被敌军俘虏的大兵瑞恩. 瑞恩被关押在一个迷宫里, 迷宫地形复杂, 但幸好麦克得到了迷宫的地形图. 迷宫的外形是 ...

  9. Bzoj 2834: 回家的路 dijkstra,堆优化,分层图,最短路

    2834: 回家的路 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 62  Solved: 38[Submit][Status][Discuss] D ...

随机推荐

  1. 时间格式 2016-08-15T16:00:00.000Z

    我修改的时间是2016-08-16(转换成Date后默认为2016-08-16 00:00:00),而我得到的时间却是2016-08-15T16:00:00.000Z 联想到我们当前的时区是+8区   ...

  2. HDU 1085 Holding Bin-Laden Captive!(母函数,或者找规律)

    Holding Bin-Laden Captive! Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Ja ...

  3. TCP连接建立与终止,及状态转换

    TCP连接建立 三路握手 三路握手发生在客户端发起connect请求到服务端accept返回中,在三路握手发生前,服务端必须准备好接受外来连接,这通常通过服务端调用 (socket.bind.list ...

  4. C开发人员眼中的SICP学习

    谈谈自己看SICP的一些体会 第一章  构造过程抽象 这一章事实上和C语言全然等价, 不打算深入学习LISP的能够高速略过. 思想上没有太多新的东西. 这一章最核心的价值就是以下3句话, 理解了这一章 ...

  5. 代码书写C++ 中调用传递与指针传递根本区别

    从概念上讲.指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变.而引用是一个别名,它在逻辑上不是独立的,它的存在具有依 ...

  6. 浅谈C#中常见的委托

    一提到委托,浮现在我们脑海中的大概是听的最多的就是类似C++的函数指针吧,呵呵,至少我的第一个反应是这样的. 关于委托的定义和使用,已经有诸多的人讲解过,并且讲解细致入微,尤其是张子阳的那一篇.我就不 ...

  7. VC 无边框对话框的任务栏右键菜单

    MFC ,基于对话框的程序,属性为:Border : none. 程序运行后,在任务栏里面点右键,不会弹出类似下面的菜单: 在对话框的OnInitDialog里面添加如下语句即可: ModifySty ...

  8. Django 之 form表单

    Django中的Form表单 1.背景 平时我们在书写form表单时,经常都是手动的去写一些input标签,让用户输入一些功能,进行一些校验的判断,等等.Django中的form表单就能够帮我们去实现 ...

  9. golang---信号signal

    golang中os/signal包的使用 chenbaoke · 2015-06-17 20:03:59 · 2748 次点击 · 预计阅读时间 1 分钟 · 不到1分钟之前 开始浏览 这是一个创建于 ...

  10. Android源代码下载过程中无法下载repo的解决方法【转】

    本文转载自:http://blog.csdn.net/shangyuan21/article/details/17618575 我们都知道下载Android源代码需要使用repo进行辅助下载,但是最进 ...