分层图初探 By cellur925
因为最近测试遇到了分层图的题目,所以稍微学了一下==。
这种题目一般是来解决最短路边权有变化/有k条免费路的问题的。他们基本都一般有两种实现方式:dp+最短路/分层图+最短路
当然你如果非要说他们是一样的我也没Fa♂反驳qwq
一、dp+最短路(以dij为例)
我们一般的球最短路都是在一维上进行的。设$dis[k]$为从起点到$k$的最短路。但是如果多了条件,比如有$k$条道路可以选择边权变化,那么就可以使用这种方法。
以P4822 [BJWC2012]冻结 这道题为例,它给出的条件是有$k$条路可以把原来的边权变为一半,那么我们就可以设$dis[k][h]$为到达$k$点,已经在$h$条路上把原来的边权变为一半的最短路。这是不是很像dp的状态的呀?是不是呀?
那么在我们平时松弛的时候,就可以看做dp的转移了。这里有两种:使用“改边卡”和不使用“改边卡”。
其他就与普通dij无异了。
当然最后的答案需要在使用0~k张改边卡间取最小值。
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring> using namespace std; int n,m,k,tot,ans=;
int head[],dis[][],vis[][];
struct node{
int to,next,val;
}edge[];
struct cellur{
int dis,p,cnt;
}; bool operator < (const cellur &a,const cellur &b)
{
return a.dis>b.dis;
} void add(int x,int y,int z)
{
edge[++tot].to=y;
edge[tot].next=head[x];
head[x]=tot;
edge[tot].val=z;
} void dijkstra()
{
memset(dis,0x3f,sizeof(dis));
priority_queue<cellur>q;
dis[][]=;q.push((cellur){,,});
while(!q.empty())
{
int u=q.top().p;
int num=q.top().cnt;q.pop();
if(vis[u][num]) continue;
vis[u][num]=;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(num<k&&dis[v][num+]>dis[u][num]+edge[i].val/)
{
dis[v][num+]=dis[u][num]+edge[i].val/;
q.push((cellur){dis[v][num+],v,num+});
}
if(dis[v][num]>dis[u][num]+edge[i].val)
{
dis[v][num]=dis[u][num]+edge[i].val;
q.push((cellur){dis[v][num],v,num});
}
}
}
} int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=;i<=m;i++)
{
int x=,y=,z=;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z),add(y,x,z);
}
dijkstra();
for(int i=;i<=k;i++)
ans=min(ans,dis[n][i]);
printf("%d",ans);
return ;
}
P4568 [JLOI2011]飞行路线 这是一个同样的题目==。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue> using namespace std; int n,m,k,s,t,tot,ans=;
int head[];
int dis[][];
bool vis[][];
struct node{
int to,next,val;
}edge[];
struct cellur{
int dis,p,cnt;
};
bool operator < (const cellur &a,const cellur &b)
{
return a.dis>b.dis;
} void add(int x,int y,int z)
{
edge[++tot].to=y;
edge[tot].next=head[x];
head[x]=tot;
edge[tot].val=z;
} void dijkstra()
{
memset(dis,0x3f,sizeof(dis));
priority_queue<cellur>q;
dis[s][]=;
q.push((cellur){,s,});
while(!q.empty())
{
int u=q.top().p;
int num=q.top().cnt;q.pop();
if(vis[u][num]) continue;
vis[u][num]=;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(num<k&&dis[v][num+]>dis[u][num])
{
dis[v][num+]=dis[u][num];
q.push((cellur){dis[v][num+],v,num+});
}
if(dis[v][num]>dis[u][num]+edge[i].val)
{
dis[v][num]=dis[u][num]+edge[i].val;
q.push((cellur){dis[v][num],v,num});
}
}
}
} int main()
{
scanf("%d%d%d",&n,&m,&k);
scanf("%d%d",&s,&t);s++;t++;
for(int i=;i<=m;i++)
{
int x=,y=,z=;
scanf("%d%d%d",&x,&y,&z);
x++;y++;
add(x,y,z);add(y,x,z);
}
dijkstra();
for(int i=;i<=k;i++)
ans=min(ans,dis[t][i]);
printf("%d",ans);
return ;
}
二、分层图最短路
这 是什么?其实给他总结成一句话:就是拆点。
我们知道dp是遍历所有的状态的,那么如果我们在开始建边的时候就考虑将所有的状态都连上边,那么之后跑裸的最短路就行了。
如何建边?我们以P2939 [USACO09FEB]改造路Revamping Trails 这道题为例。(其实这三道题基本一样,只是这道题用拆点方法写了)
题目也是要求我们有$k$条边可以把它的边权变为0。那么我们可以把每个点拆成$k+1$个点,从0到$k$,第$i$个表示到这里要用$i$次改边卡。
可以参考这段代码感性理解下。
for(int i=;i<=m;i++)
{
int x=,y=,z=;
scanf("%d%d%d",&x,&y,&z);
for(int j=;j<=k;j++)
{
add(x+j*n,y+j*n,z);add(y+j*n,x+j*n,z);//同层之间正常连边
if(j!=k)
add(x+j*n,y+(j+)*n,),add(y+j*n,x+(j+)*n,);//边权有变化了
}
}
之后跑一遍裸的dij后,我们同理在终点的所有可能状态中遍历取最小值。
for(int i=;i<=k;i++)
ans=min(ans,dis[n+i*n]);
个人认为这种方法的缺点是很难控制空间的开销。因为点数增加了$k+1$倍,边数也增加了很多倍。容易出现RE/MLE的情况。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue> using namespace std; int n,m,k,tot,ans=;
int head[],dis[],vis[];
struct node{
int to,next,val;
}edge[]; void add(int x,int y,int z)
{
edge[++tot].to=y;
edge[tot].next=head[x];
head[x]=tot;
edge[tot].val=z;
} void dijkstra()
{
priority_queue<pair<int,int> >q;
memset(dis,0x3f,sizeof(dis));
dis[]=;q.push(make_pair(,));
while(!q.empty())
{
int u=q.top().second;q.pop();
if(vis[u]) continue;
vis[u]=;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(dis[v]>dis[u]+edge[i].val)
{
dis[v]=dis[u]+edge[i].val;
q.push(make_pair(-dis[v],v));
}
}
}
} int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=;i<=m;i++)
{
int x=,y=,z=;
scanf("%d%d%d",&x,&y,&z);
for(int j=;j<=k;j++)
{
add(x+j*n,y+j*n,z);add(y+j*n,x+j*n,z);
if(j!=k)
add(x+j*n,y+(j+)*n,),add(y+j*n,x+(j+)*n,);
}
}
dijkstra();
for(int i=;i<=k;i++)
ans=min(ans,dis[n+i*n]);
printf("%d",ans);
return ;
}
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$。是不是很神奇鸭?
那么我们可以仿照之前的方法,拆点。把一个点拆成三种状态,每个状态就是到它的那条边是它本身的第几种边权。
for(int i=;i<=n;i++)
for(int j=;j<=;j++)
id[i][j]=++cnt;
for(int i=;i<=m;i++)
{
int x=,y=;
double z=;
scanf("%d%d%lf",&x,&y,&z);
add(id[x][],id[y][],z);add(id[y][],id[x][],z);
z=1.0/(-z);double tmp=fabs(z);
add(id[x][],id[y][],tmp);add(id[y][],id[x][],tmp);
z=1.0/(-z);tmp=fabs(z);
add(id[x][],id[y][],tmp);add(id[y][],id[x][],tmp);
}
然后再跑dij就行惹。
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cmath>
#define maxn 100090
#define maxm 300090 using namespace std;
const int inf=0x3f3f3f3f; int n,m,tot,cnt;
int id[maxn][],head[maxn*];
bool vis[maxn*];
double ans,dis[maxn*];
struct node{
int to,next;
double val;
}edge[maxm*]; void add(int x,int y,double z)
{
edge[++tot].to=y;
edge[tot].next=head[x];
head[x]=tot;
edge[tot].val=z;
} void dijkstra()
{
priority_queue<pair<double,int> >q;
for(int i=;i<=cnt;i++) dis[i]=inf;
q.push(make_pair(,id[][]));
dis[id[][]]=;
while(!q.empty())
{
int x=q.top().second;q.pop();
if(vis[x]) continue;
vis[x]=;
for(int i=head[x];i;i=edge[i].next)
{
int y=edge[i].to;
if(dis[y]>dis[x]+edge[i].val)
{
dis[y]=dis[x]+edge[i].val;
q.push(make_pair(-dis[y],y));
}
}
}
} int main()
{
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++)
for(int j=;j<=;j++)
id[i][j]=++cnt;
for(int i=;i<=m;i++)
{
int x=,y=;
double z=;
scanf("%d%d%lf",&x,&y,&z);
add(id[x][],id[y][],z);add(id[y][],id[x][],z);
z=1.0/(-z);double tmp=fabs(z);
add(id[x][],id[y][],tmp);add(id[y][],id[x][],tmp);
z=1.0/(-z);tmp=fabs(z);
add(id[x][],id[y][],tmp);add(id[y][],id[x][],tmp);
}
dijkstra();
ans=min(dis[id[n][]],min(dis[id[n][]],dis[id[n][]]));
printf("%.3lf\n",ans);
return ;
}
感觉这种题还是比较套路的,只要发现是分层图,建边或跑dp都不难想的qwq。还有更多拓展的题目,给自己再留下一个天坑。
(光速逃)
分层图初探 By cellur925的更多相关文章
- 【BZOJ-3627】路径规划 分层图 + Dijkstra + spfa
3627: [JLOI2014]路径规划 Time Limit: 30 Sec Memory Limit: 128 MBSubmit: 186 Solved: 70[Submit][Status] ...
- ACdream 1017 [分层图][网络流]
/* 大连热身C题 不要低头,不要放弃,不要气馁,不要慌张 题意: 给一个城市路线图,给定起点给定终点.有n个货物从起点运送到终点.城市的边是无向边. 每个货物每天如果通过某条路,那么这天这条路只能运 ...
- poj3635Full Tank?[分层图最短路]
Full Tank? Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 7248 Accepted: 2338 Descri ...
- HDU 5669 线段树优化建图+分层图最短路
用线段树维护建图,即把用线段树把每个区间都标号了,Tree1中子节点有到达父节点的单向边,Tree2中父节点有到达子节点的单向边. 每次将源插入Tree1,汇插入Tree2,中间用临时节点相连.那么T ...
- BZOJ 2763 分层图最短路
突然发现我不会分层图最短路,写一发. 就是同层中用双向边相连,用单向边连下一层 #include <cstdio> #include <algorithm> #include ...
- ZOJ-2364 Data Transmission 分层图阻塞流 Dinic+贪心预流
题意:给定一个分层图,即只能够在相邻层次之间流动,给定了各个顶点的层次.要求输出一个阻塞流. 分析:该题直接Dinic求最大流TLE了,网上说采用Isap也TLE,而最大流中的最高标号预流推进(HLP ...
- 【网络流24题】 No.15 汽车加油行驶问题 (分层图最短路i)
[题意] 问题描述:给定一个 N*N 的方形网格,设其左上角为起点◎, 坐标为( 1, 1), X 轴向右为正, Y轴向下为正, 每个方格边长为 1, 如图所示. 一辆汽车从起点◎出发驶向右下角终点▲ ...
- 【网络流24题】 No.14 孤岛营救问题 (分层图最短路)
[题意] 1944 年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛, 营救被敌军俘虏的大兵瑞恩. 瑞恩被关押在一个迷宫里, 迷宫地形复杂, 但幸好麦克得到了迷宫的地形图. 迷宫的外形是 ...
- Bzoj 2834: 回家的路 dijkstra,堆优化,分层图,最短路
2834: 回家的路 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 62 Solved: 38[Submit][Status][Discuss] D ...
随机推荐
- C++ string string string string string string string string string string
一. 初始化 string s1="i love you"; string s2(s1); //把s2初始化为string s1,注意不能写成string s2; s2(s1); ...
- FastDFS的配置、部署与API使用解读(4)FastDFS配置详解之Client配置(转)
一种方式是通过调用ClientGlobal类的初始化方法对配置文件进行加载,另一种是通过调用API逐一设置配置参数.后一种方式对于使用Zookeeper等加载属性的方式很方便. 1. 加载配置文件: ...
- ActiveMQ(一) 转
package pfs.y2017.m11.mq.activemq.demo01; import javax.jms.Connection; import javax.jms.DeliveryMode ...
- Python - scrapy安装中libxml2问题
先到 http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml 下载下面三个库的 whl,然后用pip install 来安装即可 pycurl,lxml,lib ...
- openwrt gstreamer实例学习笔记(六. gstreamer Pads及其功能)
一:概述 如我们在Elements一章中看到的那样,Pads是element对外的接口.数据流从一个element的source pad到另一个element的sink pad.pads的功能(cap ...
- 怎样搭建svn本地server,管理本地的代码
搭建svn本地server,以下是详细的步骤介绍. 一.准备工作 1.下载svnserver端:Subversion. 到官方站点(http://s version.tigris.org/)下载最新的 ...
- notpad++快捷键
Notpad++快捷键 Notepad++选中行操作 快捷键 使用技巧 作者: Rememberautumn 分类: 其他 发布时间: 2014-09-04 14:18 阅读: 60,106 微信小 ...
- C++中extern “C”含义深层探索(在原作的基础上修改) .
1. 引言 C++ 语言的创建初衷是“a better C” ,但是这并不意味着C++ 中类似C 语言的全局变量和函数所采用的编译和连接方式与C 语言完全相同.作为一种欲与C 兼容的语言,C++ 保留 ...
- 2016/05/27 php上传文件常见问题总结
php上传文件常见问题总结 投稿:hebedich 字体:[增加 减小] 类型:转载 时间:2015-02-03我要评论 这篇文章主要介绍了php上传文件常见问题总结,基本上经常碰到的问题的处理都列了 ...
- java输入输出流实例代码
1.编写一个程序,读取源代码文件的内容并在控制台输出.如果源文件不存在,则显示相应的错误信息. package src; import java.io.File; import java.io.Fil ...