魔法阵
问题描述
魔法师小蓝为了营救自己的朋友小 QQ,来到了敌人布置的魔法阵。魔法阵可以看作是一幅具有 NN 个结点 MM 条边的无向图,结点编号为 0,1,2,...,N−10,1,2,...,N−1,图中没有重边和自环。敌人在每条边上都布置了陷阱,每条边都有一个伤害属性 ww,每当小蓝经过一条边时就会受到这条边对应的 ww 的伤害。小蓝从结点 00 出发,沿着边行走,想要到达结点 N−1N−1 营救小 QQ。
小蓝有一种特殊的魔法可以使用,假设一条路径按照顺序依次经过了以下 LL 条边:e1,e2,...,eLe1,e2,...,eL (可以出现重复的边),那么期间小蓝受到的总伤害就是 P=∑i=1Lw(ei)P=i=1∑Lw(ei),w(ei)w(ei) 表示边 eiei 的伤害属性。如果 L≥KL≥K,那么小蓝就可以从这 LL 条边当中选出连续出现的 KK 条边 ec,ec+1,...,ec+K−1ec,ec+1,...,ec+K−1 并免去在这 KK 条边行走期间所受到的伤害,即使用魔法之后路径总伤害变为 P′=P−∑i=cc+K−1w(ei)P′=P−i=c∑c+K−1w(ei)。注意必须恰好选出连续出现的 KK 条边,所以当 L<KL<K 时无法使用魔法。
小蓝最多只可以使用一次上述的魔法,请问从结点 00 出发到结点 N−1N−1 受到的最小伤害是多少?题目保证至少存在一条从结点 00 到 N−1N−1 的路径。
输入格式
第一行输入三个整数,N,K,MN,K,M,用空格分隔。接下来 MM 行,每行包含三个整数 u,v,wu,v,w,表示结点 uu 与结点 vv 之间存在一条伤害属性为 ww 的无向边。
输出格式
输出一行,包含一个整数,表示小蓝从结点 00 到结点 N−1N−1 受到的最小伤害。
样例输入1
4 2 3
0 1 2
1 2 1
2 3 4
样例输出1
2
样例输入2
2 5 1
0 1 1
样例输出2
0
样例说明
样例 11,存在路径:0→1→2→30→1→2→3,K=2K=2,如果在 0→1→20→1→2 上使用魔法,那么答案就是 0+0+4=40+0+4=4;如果在 1→2→31→2→3 上使用魔法,那么答案就是 2+0+0=22+0+0=2。再也找不到比 22 还小的答案了,所以答案就是 22。
样例 22,存在路径:0→1→0→1→0→10→1→0→1→0→1,K=5K=5,这条路径总计恰好走了 55 条边,所以正好可以用魔法消除所有伤害,答案是 00。
评测用例规模与约定
对于 3030% 的评测用例,1≤N≤201≤N≤20。
对于 5050% 的评测用例,1≤N≤1001≤N≤100。
对于 100100% 的评测用例,1≤N≤10001≤N≤1000,1≤M≤N×(N−1)21≤M≤2N×(N−1),1≤K≤101≤K≤10,0≤u,v≤N−10≤u,v≤N−1,1≤w≤10001≤w≤1000。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.*;
public class Main {
static int N=1010;
static int n,m,k;
static int id=1;
static int cnt=-1;
static int a[][]=new int[N][N];
static int dist0[]=new int[N];
static int dist1[]=new int[N];
static boolean f[]=new boolean[N];
static boolean f1[]=new boolean[N];
static int dp[][]=new int[N][N];
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws IOException {
StringTokenizer st=new StringTokenizer(br.readLine());
n=Integer.parseInt(st.nextToken());
k=Integer.parseInt(st.nextToken());
m=Integer.parseInt(st.nextToken());
for (int i = 0; i < N; i++) {
Arrays.fill(a[i],Integer.MAX_VALUE);
a[i][i]=0;
}
for (int i = 0; i < m; i++) {
st=new StringTokenizer(br.readLine());
int u=Integer.parseInt(st.nextToken()),v=Integer.parseInt(st.nextToken());
int w=Integer.parseInt(st.nextToken());
a[u][v]=w;
a[v][u]=a[u][v];
}
//定义dist0[i]为从零到i的最短路径 dist1[i]为从i到n-1的最短路径
//我们其实需要找的是两个点u和v 使得dist0[u]+0+dist1[v]
//0表示u到v恰好经过了k条边 免去伤害 这些边的权值不重要
//现在求dist0[u]+dist1[v]的最小值
//先求dist0[u]的最小值
Arrays.fill(dist0, Integer.MAX_VALUE);
dist0[0]=0;
PriorityQueue<int[]> priorityQueue=new PriorityQueue<>((a,b)->a[1]-b[1]);
priorityQueue.add(new int[]{0,0});
while(!priorityQueue.isEmpty()){
int no[]=priorityQueue.poll();
int u=no[0];
if(!f[u]){
f[u]=true;
for (int i = 0; i < n; i++) {
if(f[i])continue;
if(a[u][i]!=Integer.MAX_VALUE){
if(dist0[i]>dist0[u]+a[u][i]){
dist0[i]=dist0[u]+a[u][i];
priorityQueue.add(new int[]{i,dist0[i]});
}
}
}
}
}
//定义f[i][v]为某个节点经过i条边到v dist0[u]的最小值
for (int i = 0; i < n; i++) {
Arrays.fill(dp[i], Integer.MAX_VALUE);
dp[0][i]=dist0[i];//在i之前有零条边 说明i就是起点
}
for (int i = 1; i <= k; i++) {//枚举有几条边
for (int v = 0; v < n; v++) {//枚举v
for (int pre = 0; pre < n; pre++) {//枚举v的前一个点
if(v==pre)continue;
if(a[pre][v]!=Integer.MAX_VALUE){
dp[i][v]=Math.min(dp[i][v], dp[i-1][pre]);
}
}
}
}
//再求dist1[v]的最小值
PriorityQueue<int[]> priorityQueue2=new PriorityQueue<>((a,b)->a[1]-b[1]);
priorityQueue2.add(new int[]{n-1,0});
Arrays.fill(dist1, Integer.MAX_VALUE);
dist1[n-1]=0;
while(!priorityQueue2.isEmpty()){
int no[]=priorityQueue2.poll();
int u=no[0];
if(!f1[u]){
f1[u]=true;
for (int i = 0; i < n; i++) {
if(a[u][i]!=Integer.MAX_VALUE && dist1[i]> dist1[u]+a[u][i]){
dist1[i]=dist1[u]+a[u][i];
priorityQueue2.add(new int[]{i,dist1[i]});
}
}
}
}
int res=Integer.MAX_VALUE;
for (int i = 0; i < n; i++) {
res=Math.min(res, dp[k][i]+dist1[i]);
}
bw.write(res+"");
br.close();
bw.flush();
bw.close();
}
}