BZOJ4681 [Jsoi2010]旅行

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并不打算带着它旅行,于是她会在家里完成想要的道路交换后再出门。假设WJI的旅行途中不会有其他小动物进行道路交换来破坏她设计好的路线。为了尽快达到目的地,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。显然没有比这更优的方案了。


题意:

       给定一张无向图,求在最多交换k条边长度的情况下1号点到n号点的最短路。

题解:

       第一眼以为是费用流,看了题解才知道是DP神题……

       将所有边按照边权排序,如果用一个01串来表示每条边是否被使用,则交换边权后的01串是一个前面全是1,后面有0有1的串。那么我们枚举这个分界线L在哪里。对于每条路径,我们可以选择直接走过去,或者和前L条边中的一条交换权值。于是考虑用f[i][j][k]表示到第i个点,前L条边用了j条,交换了k次的最小代价。转移有三种情况:

       1、这条边是前L条边中的一条。f[arr[e]][j+1][k]=min(f[arr[e]][j+1][k],f[i][j][k]+weight[j+1]) 这是因为边e和边j+1都是一定会被用到的,先用哪个无所谓,但是为了方便转移前L条边只能被从小到大使用。

        2、这条边不在前L条边中。

              1)f[arr[e]][j][k]=min(f[arr[e]][j][k],f[i][j][k]+weight[e]) 直接使用边e

              2)f[arr[e]][j+1][k+1]=min(f[arr[e]][j+1][k+1],f[i][j][k]+weight[j+1]) 将边e和前L条边中的一条交换。

       转移时候用spfa或者dijstra搞一下就OK。

#include<bits/stdc++.h>
using namespace std;
const int N=55;
const int M=310;

int n,m,E,ans,mogic;
int fir[N],nex[M],len[M],arr[M],ord[M],f[N][N][N],vis[N][N][N];
struct Edge {
	int u,v,l;
}a[M];
struct Statu {
	int x,y,z;
};
queue<Statu> q;

bool cmp(Edge x,Edge y) {
	return x.l<y.l;
}

void Add_Edge(int x,int y,int l,int o) {
	nex[++E]=fir[x];
	fir[x]=E; arr[E]=y;
	ord[E]=o; len[E]=l;
}

void Solve(int L) {
	memset(f,0x3f,sizeof(f));
	f[1][0][0]=0; q.push((Statu){1,0,0});
	while (!q.empty()) {
		Statu t=q.front(); q.pop();
		int x=t.x,y=t.y,z=t.z;
		vis[x][y][z]=0;
		for (int i=fir[x];i;i=nex[i]) {
			if (ord[i]<=L) {
				if (f[arr[i]][y+1][z]>f[x][y][z]+a[y+1].l) {
					f[arr[i]][y+1][z]=f[x][y][z]+a[y+1].l;
					if (!vis[arr[i]][y+1][z]) {
						vis[arr[i]][y+1][z]=1;
						q.push((Statu){arr[i],y+1,z});
					}
				}
			}
			else {
				if (z<mogic&&y<L) {
					if (f[arr[i]][y+1][z+1]>f[x][y][z]+a[y+1].l) {
						f[arr[i]][y+1][z+1]=f[x][y][z]+a[y+1].l;
						if (!vis[arr[i]][y+1][z+1]) {
							vis[arr[i]][y+1][z+1]=1;
							q.push((Statu){arr[i],y+1,z+1});
						}
					}
				}
				if (f[arr[i]][y][z]>f[x][y][z]+len[i]) {
					f[arr[i]][y][z]=f[x][y][z]+len[i];
					if (!vis[arr[i]][y][z]) {
						vis[arr[i]][y][z]=1;
						q.push(Statu{arr[i],y,z});
					}
				}
			}
		}
	}
	for (int j=0;j<=mogic;j++) ans=min(ans,f[n][L][j]);
}

int main() {
	scanf("%d%d%d",&n,&m,&mogic);
	for (int i=1;i<=m;i++) scanf("%d%d%d",&a[i].u,&a[i].v,&a[i].l);
	sort(a+1,a+m+1,cmp);
	for (int i=1;i<=m;i++) {
		Add_Edge(a[i].u,a[i].v,a[i].l,i);
		Add_Edge(a[i].v,a[i].u,a[i].l,i);
	}
	ans=0x3f3f3f3f;
	for (int i=0;i<=m;i++) Solve(i);
	printf("%d",ans);
	return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值