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

魔法阵

问题描述

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

       
}
相关推荐
致Great1 小时前
Pi 的上下文压缩,到底是怎么工作的?
算法
watersink2 小时前
机器学习聚类算法
算法·机器学习·聚类
luj_17683 小时前
桥牌思维启示:系统设计的模块化架构
c语言·开发语言·c++·经验分享·算法
饼饼学习空间智能5 小时前
家庭服务机器人训练数据怎么积累?仿真、真实采集与持续学习的技术路线分析
人工智能·算法·机器学习
蛋先生DX6 小时前
你瘦不下来但大模型可以:量化原理了解一下
深度学习·算法·llm
牛阿大7 小时前
LQR算法
算法
(╹◡╹)8 小时前
18.剪枝
算法·机器学习·剪枝
Fa_Mian_Tuan9 小时前
图论基础|邻接矩阵超详细讲解(含无向/有向/带权图+完整可运行C语言代码)
c语言·数据结构·笔记·算法·图论
hanhahai9 小时前
指针与函数(函数指针与指针函数)
算法