bzoj4681: [Jsoi2010]旅行(最短路)

本文介绍了一种解决特定旅行问题的算法,通过拆点的方法找到使用最多K次道路长度交换魔法后,从起点到终点的最短路径。该算法利用了最短路径树的概念,并通过枚举关键边的数量优化解决方案。

4681: [Jsoi2010]旅行

Time Limit: 30 Sec
Memory Limit: 256 MB

Description

WJJ喜欢旅游,这次她打算去一个据说有很多漂亮瀑布的山谷玩。
WJJ事先得到了一张地图,上面标注了N(1< = N< = 50)个小动物的聚居地,也就是一个个的小村落。其中第1个村庄是WJJ现在住的地方,第N个村庄是WJJ打算去的地方。这些村庄之间有M(1< = M< = 150)条双向道路连接着,第j条双向道路恰好直接连接两个小村庄A,B,长度为C(1< = A,B < = N,Ai<>B, 1< = C< = 1000)。道路有的是隧道,有的是栈桥,地图上那些看起来在村庄之外交叉的路实际上并不相交——也就是说,如果把这些小村落和双向道路构成的道路网看作图论意义上的图,我们不保证它是平面图,也不保证它没有重边。不过,有一点还是可以保证的:WJJ细心地验证过,从它居住的村落一定能走到她想去的那个山谷。在WJJ所在的神奇世界中,每只小动物都可以借助仙人掌来施放魔法,其中之一是,交换世界中任意两条双向道路的长度,同时保持其他道路的长度不变。按WJJ目前的魔法水平,她最多能使用K(1< = K< = 20)次这种道路长度交换魔法。可惜的是,由于仙人掌刺比较多,WJJ并不打算带着它旅行,于是她会在家里完成想要的道路交换后再出门。假设WJJ的旅行途中不会有其他小动物进行道路交换来破坏她设计好的路线。为了尽快达到目的地,WJJ希望她需要走的总距离越短越好。也就是说,使用最多K次魔法后,从村落1到村落N的最短距离是多少?

Input

第1行:3个用空格隔开的整数N,M,K
第2行:M+1行:每行是3个整数,用空格隔开,分别表示A,B, C

Output

1个整数,表示使用最多K次魔法后,村落1和村落N之间的最短距离。

Sample Input
5  5  2
1  2  10
2  5  10
1  3  4
3  4  2
4  5  1
Sample Output
3

一个可行的方案是,对调第1条边和第4条边的长度,再对调第1条边和第5条边的长度。对调后的最短路径为1–>2–>5,长度为3。显然没有比这更优的方案了。











解:

震惊,这最短路简直神了。本来想的最短路树,但是可以交换的边特别多,似乎不是很支持。hzwer的想法也很好,就是无视k条边的最短路。

简单来说,这么少的点,如果要跑图论算法那么只能想到拆点。从hzwer的想法出发。我们发现一个性质,就是我们最后的答案路线一定会有前 L L 小的路线。我们枚举L,无视前 L L 小的边跑最短路。这样的话加上交换次数,我们就要把点拆成三维的。用dis(x,y,z)表示到第 x x 号点路径上有y条前 L L 小的边,交换了z次的最短路。而且这样做的好处是我们默认前 L L 条边都在最短路上,所以我们并不需要考虑这L条边出现的顺序。
于是我们有了这几个转移:

如果下一条边在前 L L 条边,
dis[eg[i].to][y+1][z]<——dis[x][y][z]+data[y+1].len

如果下一条边不在前L条里:
我们有两种选择(交换或者不交换):
dis[eg[i].to][y+1][z+1]<——dis[x][y][z]+data[y+1].len
dis[eg[i].to][y][z]<——dis[x][y][z]+data[(i+1)/2].len

我们只要枚举 L L 就好了。

code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
struct lxy{
    int to,next;
}eg[305];
struct lxy1{
    int op,ed,len;
    bool operator < (const lxy1 &QAQ)const{
      return len<QAQ.len;
    }
}data[155];
struct colder{
    int x,y,z,dis;
    bool operator < (const colder &QAQ)const{
      return dis>QAQ.dis;
    }
}yyy;

priority_queue <colder> d;

int n,m,head[55],k,cnt,ans=0x7f7f7f7f;
int dis[55][155][155];
bool vis[55][155][155];

void add(int op,int ed){
    eg[++cnt].to=ed;
    eg[cnt].next=head[op];
    head[op]=cnt;
}

void coldcold(int l)
{
    memset(vis,0,sizeof(vis));
    memset(dis,0x3f,sizeof(dis));
    dis[1][0][0]=0;yyy.x=1;yyy.y=0;yyy.z=0;yyy.dis=0;d.push(yyy);
    while(!d.empty()){
        int x=d.top().x,y=d.top().y,z=d.top().z;
        vis[x][y][z]=1;d.pop();
        for(int i=head[x];i!=-1;i=eg[i].next)
        {
            if(i<=l*2){
                if(y<l&&dis[eg[i].to][y+1][z]>dis[x][y][z]+data[y+1].len)
                   dis[eg[i].to][y+1][z]=dis[x][y][z]+data[y+1].len,yyy.x=eg[i].to,yyy.y=y+1,yyy.z=z,yyy.dis=dis[eg[i].to][y+1][z],d.push(yyy);
            }
            else{
                if(y<l&&z<k&&dis[eg[i].to][y+1][z+1]>dis[x][y][z]+data[y+1].len)
                  dis[eg[i].to][y+1][z+1]=dis[x][y][z]+data[y+1].len,yyy.x=eg[i].to,yyy.y=y+1,yyy.z=z+1,yyy.dis=dis[eg[i].to][y+1][z+1],d.push(yyy);
                if(dis[eg[i].to][y][z]>dis[x][y][z]+data[(i+1)/2].len)
                  dis[eg[i].to][y][z]=dis[x][y][z]+data[(i+1)/2].len,yyy.x=eg[i].to,yyy.y=y,yyy.z=z,yyy.dis=dis[eg[i].to][y][z],d.push(yyy);
            }
        }
        while(!d.empty()&&vis[d.top().x][d.top().y][d.top().z]==1) d.pop();
    }
    for(int i=0;i<=k;i++) ans=min(ans,dis[n][l][i]);
}

int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=m;i++)
      scanf("%d%d%d",&data[i].op,&data[i].ed,&data[i].len);
    sort(data+1,data+1+m);
    for(int i=1;i<=m;i++){
      add(data[i].op,data[i].ed);
      add(data[i].ed,data[i].op);
    }
    for(int i=0;i<=m;i++) coldcold(i);
    printf("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值