最短路总结
Posted wtz2333
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最短路总结相关的知识,希望对你有一定的参考价值。
这一段时间复习了一下最短路,做了几道非常典型特别考察最短路性质的题
1.P1144 最短路计数
这个题主要考察对松弛操作的理解。
关键代码
if(dis[v] > dis[u] + 1)
{
dis[v] = dis[u] + 1;
ans[v] = ans[u];
q.push(make_pair(dis[v],v));
}
else if(dis[v] == dis[u] + 1)
{
ans[v] += ans[u];
ans[v] %= mo;
}
2.CF786B Legacy
线段树优化建边,对于一个点,与一个连续区间里的点连边,可以利用线段树的特性来降低连边复杂度。
void build1(int &p,int l,int r)
{
if(l==r)
{
p=l;return;
}
p=++tot;
int mid=l+r>>1;
build1(lc[p],l,mid);
build1(rc[p],mid+1,r);
add(p,lc[p],0);add(p,rc[p],0);
}
3.P1772 [ZJOI2006]物流运输
这是一道DP加最短路的题目,由于数据很小,我们的复杂度可以很高
设计这样的一个DP。f[i] 表示前i天的花费,考虑转移,考虑第j天是否改变航线
方程为:f[i] = min(f[i],f[j-1] + (i-j+1) * x + k);
对于每一次变化,我们都要求一次最短路。
f[0] = -k;
for(int i = 1;i <= n;i ++)
{
for(int j = 1;j <= m ;j++)flag[j] = 0;
for(int j = i;j >= 1;j--)
{
for(int a = 1;a <= m ;a++)
if(pd[a][j])flag[a] = 1;
int x = spfa(1);
if(x >= dis[0])break;
f[i] = min(f[i],f[j-1] + (i-j+1)*x + k);
}
}
4.P2868 [USACO07DEC]观光奶牛Sightseeing Cows
01分数规划,懒得说了大家自己百度吧,主要是二分加图论结合。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
inline int read()
{
char ch=getchar();int x=0,f=1;
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch<='9' && ch>='0') {
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
inline ll readl()
{
char ch=getchar();ll x=0,f=1;
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch<='9' && ch>='0') {
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
const int maxn = 5001;
int n,m;
int a[maxn],vis[maxn],num[maxn];
int l,pre[maxn<<1],last[maxn],other[maxn<<1];
double len[maxn<<1],dis[maxn];
queue<int> q;
void add(int x,int y,int z)
{
l++;
pre[l] = last[x];
last[x] = l;
other[l] = y;
len[l] = z;
}
bool spfa(int x,double y)
{
memset(num,0,sizeof num);
memset(vis,0,sizeof vis);
vis[x] = 1;
memset(dis,0,sizeof dis);
dis[x] = 0;
q.push(x);
while(!q.empty())
{
int u = q.front();
q.pop();
vis[u] = 0;
if(num[u] >= n)return 1;
for(int p = last[u];p;p = pre[p])
{
int v = other[p];
if(dis[v] > dis[u] + y*len[p] - a[u])
{
dis[v] = dis[u] + y*len[p] - a[u];
if(vis[v] == 0)
{
vis[v] = 1;
num[v] ++;
if(num[v] >= n)return 1;
q.push(v);
}
}
}
}
return 0;
}
bool check(double x)
{
//cout<<x<<endl;
for(int i = 1;i <= n ;i++)
if(spfa(i,x))return 1;
return 0;
}
int main(){
n = read(),m = read();
for(int i = 1;i <= n ;i++)a[i] = read();
for(int i = 1;i <= m;i++)
{
int a = read(),b = read(),c = read();
add(a,b,c);
}
double l = 0,r = 1000000,ans = 0;
while(r - l >= 0.0001)
{
double mid = (l + r) / 2;
if(check(mid))
l = mid + 0.0001,ans = mid;
else r = mid - 0.0001;
}
printf("%.2lf",ans);
return 0;
}
5.P2939 [USACO09FEB]改造路Revamping Trail
分层图最短路板子题,观察题面,发现k很小,我们就可以建k张图,图与图之间建零花销边,最后跑一边最短路就OK了
for(int i = 1;i <= m ;i++)
{
int a = read(),b = read(),c = read();
add(a,b,c);add(b,a,c);
for(int j = 1;j <= k ;j ++)
{
add(a + j*n,b + j*n,c);
add(b + j*n,a + j*n,c);
add(a + (j-1)*n,b + j*n,0);
add(b + (j-1)*n,a + j*n,0);
}
}
6.P1613 跑路
倍增思想与Floyd的结合(f[i][j][p] = (f[i][k][p-1] , f[k][j][p-1]))
(f[i][j][p]) 表示i到j距离是否为(2^{p})。
for(int p = 1;p <= 64;p ++){
for(int k = 1;k <= n ;k ++){
for(int i = 1;i <= n ;i ++){
for(int j = 1;j <= n ;j ++){
if(f[i][k][p-1] && f[k][j][p-1]){
f[i][j][p] = 1;
}
}
}
}
}
7.P3393 逃离僵尸岛
最短路常见套路,加一个虚拟节点来简化问题,将所有危险城市用虚拟节点连起来统一处理距离小于等于s的,由于多建了一些边,所以要开大一些空间。
for(int i = 1;i <= k ;i ++){
int c = read();flag[c] = 1;
add(n+1,c);add(c,n+1);
}
for(int i = 1;i <= n ;i ++)w[i] = p;
dij(n+1);
for(int i = 1;i <= n ;i ++){
if(dis[i] <= s+1){
w[i] = q;
}
}
8.P1606 [USACO07FEB]白银莲花池Lilypad Pond
简单分析,发现这就是一个最短路模型,对于第一问怎么建图都ok,但对于第二问如果将水和荷叶相连会导致计数重复,所以将水和可以通过荷叶到达的水相连,再跑最短路计数
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 7;
int n,m,st,ed;
int l,pre[maxn],last[maxn],other[maxn],len[maxn];
int mp[1010][1010],id[1010][1010];
int flag[1010][1010];
ll cnt[maxn];
int vis[maxn],que[maxn],dis[maxn];
int dx[9]={0,-2,-1,1,2,2,1,-1,-2};
int dy[9]={0,1,2,2,1,-1,-2,-2,-1};
void add(int x,int y,int z){
l ++;
pre[l] = last[x];
last[x] = l;
other[l] = y;
len[l] = z;
}
void dfs(int p,int x,int y){
if(flag[x][y])return ;
flag[x][y] = 1;
for(int i = 1;i <= 8;i ++){
int xx = x + dx[i];
int yy = y + dy[i];
if(xx <= 0 || yy <= 0 ||xx > n || yy > m || flag[xx][yy])continue;
if(mp[xx][yy] == 1)dfs(p,xx,yy);
flag[xx][yy] = 1;
add(p,id[xx][yy],1);
}
}
void spfa(int x){
memset(dis,0x3f,sizeof dis);
dis[x] = 0;
que[1] = x;
vis[x] = 1;
cnt[x] = 1;
int h = 0,t = 1;
while(h != t){
h ++;
int u = que[h];
vis[u] = 0;
for(int p = last[u];p;p = pre[p]){
int v = other[p];
if(dis[v] == dis[u] + len[p]){
cnt[v] += cnt[u];
}
if(dis[v] > dis[u] + len[p]){
cnt[v] = cnt[u];
dis[v] = dis[u] + len[p];
if(!vis[v]){
vis[v] = 1;
que[++t] = v;
}
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n ;i ++){
for(int j = 1;j <= m;j ++){
scanf("%d",&mp[i][j]);
id[i][j] = (i-1)*m + j;
if(mp[i][j] == 3)st = id[i][j];
if(mp[i][j] == 4)ed = id[i][j];
}
}
for(int i = 1;i <= n;i ++){
for(int j = 1;j <= m;j ++){
if(mp[i][j] == 2 || mp[i][j] == 4)continue;
memset(flag,0,sizeof flag);
dfs(id[i][j],i,j);
}
}
spfa(st);
if(dis[ed] == dis[0]){
cout<<-1<<endl;return 0;
}
else {
cout<<dis[ed] - 1<<endl;
cout<<cnt[ed]<<endl;
}
return 0;
}
9.T51485 键盘
题解:分析题目,很像一个DP,但是存在删除键,所以存在后效性,但是可以对每个操作连边跑最短路。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 7;
int dis[maxn],vis[maxn];
int n,x;
void spfa(){
memset(dis,63,sizeof dis);
queue <int> q;
if(x >= n){
dis[0] = 3;dis[n] = x - n;
q.push(0);
vis[0] = 1;
}
else {
dis[x] = 0;
q.push(x);
vis[x] = 1;
}
while(!q.empty()){
int u = q.front();
q.pop();
vis[u] = 0;
if(u < n &&dis[u + 1] > dis[u] + 1){
dis[u + 1] = dis[u] + 1;
if(!vis[u + 1]){
vis[u + 1] = 1;
q.push(u + 1);
}
}
if(u > 1&&dis[u - 1] > dis[u] + 1){
dis[u - 1] = dis[u] + 1;
if(!vis[u - 1]){
vis[u - 1] = 1;
q.push(u - 1);
}
}
if(u > 0){
for(int i = 2;u*(i-1) <= n;i ++){
if(u * i >= n&&dis[n] > dis[u] + 2*(i + 1) + u*i - n){
dis[n] = dis[u] + 2*(i + 1) + u*i - n;
}
if(u * i < n && dis[u*i] > dis[u] + 2*(i + 1)){
dis[u*i] = dis[u] + 2*(i + 1);
if(vis[u*i] == 0){
vis[u*i] = 1;
q.push(u*i);
}
}
}
}
}
}
int main()
{
scanf("%d%d",&x,&n);
spfa();
printf("%d",dis[n]);
return 0;
}
以上是关于最短路总结的主要内容,如果未能解决你的问题,请参考以下文章