【ACPC2013】馬里奧賽車(01揹包)

Baling_haku發表於2020-11-13

這裡終究是個技術社群,老是在這裡寫中二少年的風花雪月也不大好,來寫一篇題解。
這題目混進了軟體工程大一新生的java基礎題目裡,與其他設計類、熟悉語法的題目格格不入不講武德我一查來源,好傢伙是中東+非洲的大學生程式競賽,老師找到這題目也是不容易,而且各大OJ上也沒有。


題目大意

路上有n個車站,你有m個硬幣,每個硬幣都有自己的能量和使用成本,每組資料有成本限制l。
從a車站跳躍到b車站的方法是:使用一些硬幣的組合,使這些硬幣的能量恰好等於兩車站距離(座標之差之絕對值)。
對於一次跳躍,每硬幣只能用一次且使用的硬幣成本之和不超過l。
你的目的是從座標最小的車站跳躍到座標最大的車站,使跳躍次數儘可能少。

輸入格式

第一行給定資料組數T
接下來T組資料,每組資料:
第一行n(<=100),m(<=100),l(<=1000)
接下來一行中n個數,分別是n個車站的座標(無序)
接下來m行,每行兩個數,代表該硬幣成本與能量

輸出格式

T行,代表每組資料最小跳躍次數,若不能到達輸出-1


這道題目,首先想到最後找次數是最短路,但由於是記錄次數,則此題無邊權(邊權相等),所以我直接廣搜。
接下來主要思考怎樣連邊
其實題目說得很直白了,看到恰好二字,想到NOIP2018d1t2,判斷將不同面額的貨幣能否湊為某特定值,使用了完全揹包(因為每種貨幣數量不限)。
然而此題又增加了成本限制,所以不能用單純的01揹包解決。顯然,對於待湊路程的最優情況就是成本最低,所以與一般意義上揹包湊出最大價值不同,這道題需要找到最小成本
那麼題意中硬幣的成本轉化為一般揹包的價值
題意中硬幣的能量轉化為一般揹包的重量
既然追求最小,則需要初始化最小可能路程~最大可能路程為無窮大。這樣資料更新只能從容量為0的狀態開始,凡是不能正好湊出的路程數,成本均為無窮大。
狀態f[j]代表跳躍j路程所需最小成本
則狀態轉移方程f[j]=min(f[j],f[j-ci]+vi)
這樣當僅f[abs(兩座標之差)]<=l時,兩車站連邊。


由於資料較小,直接使用鄰接矩陣。

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        int tt=sc.nextInt();
        int[] a=new int[105];
        int[] vis=new int[105];
        int[] f=new int[1005];
        int[][] x=new int[105][105];
        while(tt-->0){
            int n=sc.nextInt();
            for(int i=1;i<=n;i++)
                for(int j=1;j<=n;j++)x[i][j]=0;
            for(int i=1;i<=n;i++)vis[i]=0;
            int m=sc.nextInt();
            int l=sc.nextInt();
            int max=-1<<20,min=1<<20,maxn=0,minn=0;
            for(int i=1;i<=n;i++){
                int tmp=sc.nextInt();
                if(tmp>max){max=tmp;maxn=i;}
                if(tmp<min){min=tmp;minn=i;}
                a[i]=tmp;
            }
            for(int i=1;i<=max-min;i++)f[i]=1<<30;
            for(int i=1;i<=m;i++){//DP
                int w=sc.nextInt();
                int v=sc.nextInt();
                for(int j=max-min;j>=v;j--)
                    f[j]=Math.min(f[j],f[j-v]+w);
            }
           /* for(int i=1;i<=max-min;i++) System.out.print(f[i]+" ");*/
            for(int i=1;i<n;i++)//連邊
                for(int j=i+1;j<=n;j++)
                    if(f[Math.abs(a[i]-a[j])]<=l){x[i][j]=1;x[j][i]=1;}
            /*for(int i=1;i<=n;i++) {
                for (int j = 1; j <= n; j++) System.out.print(x[i][j] + " ");
                System.out.println();
            }*/
            Queue<Integer> q = new LinkedList<Integer>();//java的佇列宣告還找了半天
            Queue<Integer> qx = new LinkedList<Integer>();
            q.add(minn); qx.add(0); vis[minn]=1;//準備廣搜
            int fl=0;
            while(!q.isEmpty()){
                int e=q.poll(); int ex=qx.poll();
                if(e==maxn){
                    System.out.println(ex);
                    fl=1;
                    break;
                }
                for(int i=1;i<=n;i++)
                    if(x[e][i]==1&&vis[i]==0){q.add(i);qx.add(ex+1);vis[i]=1;}
            }
            if(fl==0)System.out.println("-1");
        }
    }
}


注意揹包DP中,f陣列中每個值都是最優的,不只是f[揹包容量]有用。

相關文章