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

魔法阵

问题描述

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

       
}
相关推荐
phltxy6 小时前
C语言操作符详解
java·c语言·算法
aqiu1111117 小时前
【LeetCode 902】最大为 N 的数字组合 - 详细题解与数位组合思路
数据结构·算法·leetcode·蓝桥杯·数位dp
辰烨chenye7 小时前
LeetCode Hot 100 题解 · 哈希篇
算法·leetcode·哈希算法
罗西的思考8 小时前
DreamZero 与 DreamDojo:世界模型与策略的分层协同综合分析与对比
人工智能·算法·机器学习
玖玥拾8 小时前
LeetCode 219 存在重复元素 II
算法·leetcode·哈希算法·散列表
知无不研9 小时前
c语言中循环的介绍与简单应用
c语言·开发语言·算法·循环·for·while
a1879272183110 小时前
【算法】动态规划第四篇:背包收官——min 哨兵、计数世界与组合排列分水岭
算法·leetcode·动态规划·dp·01背包·算法讲解·决策合并
2601_9622974811 小时前
在python3中、下列输出变量a的正确写法是_2020超星大数据Python免费答案
数据结构·python·算法·编程·字符串操作
辰烨chenye11 小时前
LeetCode Hot 100 题解 · 子串篇
算法·leetcode·职场和发展