图论+动态规划——魔法阵

魔法阵

问题描述

魔法师小蓝为了营救自己的朋友小 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∑L​w(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−1​w(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();
    }
    
    

       
}
相关推荐
大爱编程♡1 小时前
C++基础-类和对象
c++·算法
ysa0510301 小时前
【板子】费用流
算法·深度优先·图论
玖玥拾1 小时前
LeetCode 80 删除有序数组中的重复项 II
算法·leetcode
青山木2 小时前
Hot 100 --- 电话号码的字母组合
java·数据结构·算法·leetcode·逻辑回归
闪电悠米2 小时前
力扣hot100-240.搜索二维矩阵2-单调性剪枝详解
数据结构·算法·leetcode·矩阵·哈希算法
imuliuliang2 小时前
关于基于图论的最短路径算法性能对比分析7
算法
hurrycry_小亦2 小时前
洛谷题目:P1233 [ICPC 2001 Taejon R] 木棍加工 题解(本题较难)
c++·算法·贪心算法·动态规划
imuliuliang2 小时前
关于从栈与队列看算法思维的演化路径7
算法
春生野草3 小时前
个人笔记--大顶堆和基数排序
java·数据结构·算法