【程式碼源】每日一題 國家鐵路

self_disc發表於2022-04-28

 2022.04.28

題目連結:國家鐵路 - 題目 - Daimayuan Online Judge

題目描述

dls的算競王國可以被表示為一個有 HH行和 WW列的網格,我們讓 (i,j)(i,j)表示從北邊第 ii行和從西邊第 jj列的網格。最近此王國的公民希望國王能夠修建一條鐵路。

鐵路的修建分為兩個階段:

1:從所有網格中挑選2個不同的網格,在這兩個網格上分別修建一個火車站。在一個網路上修建一個火車站的代價是 Ai,j;

2:在這兩個網格間修建一條鐵軌,假設我們選擇的網格是 (x1,y1)和(x2,y2),其代價是 C∗(|x1−x2|+|y1−y2|);

dls的願望是希望用最少的花費去修建一條鐵路造福公民們。現在請你求出這個最小花費。

題目輸入

第一行輸入三個整數分別代表H,W,C

接下來H行,每行W個整數,代表Ai,j

題目輸出

輸出一個整數代表最小花費

資料範圍

2≤H,W≤1000

1≤C≤1e9

1≤Ai,j≤1e9

樣例輸入:

3 4 2
1 7 7 9
9 6 3 7
7 8 6 4

樣例輸出:

10 

 本題要求最小花費,即兩個站點的費用+鐵路費用(A(x1,y1)+A(x2,y2)+ C∗(|x1−x2|+|y1−y2|))。考慮去掉絕對值,轉化成對兩個點對應的費用的最小值問題。

(x1,y1) , (x2,y2)的位置關係有以下兩種:

1.   x2>=x1且y2>=y1 (x1!=x2&&y1!=y2)(主對角線上)

        費用: A(x1,y1)+A(x2,y2)+ C∗(|x1−x2|+|y1−y2|)

                =A(x1,y1)+A(x2,y2) + C∗(x2 − x1) + C*(y2 − y1)

                =A(x1,y1) - C ∗ (x1 + y1) + A(x2,y2) + C*(x2 + y2)

分成兩部分,分別求最小值

 

 【程式碼源】每日一題 國家鐵路

(圖有點醜了,將就看看吧) 

 考慮列舉(x2,y2)的位置,滿足x2>=x1且y2>=y1關係的點在圖中陰影部分,快速求(x1,y1)的最佳位置,那麼如何用O(1)的複雜度查詢(x1,y1)的最佳位置呢?二維字首最小值

2.  x1>=x2且y2>=y1 (x1!=x2&&y1!=y2)(副對角線上)

        費用: A(x1,y1)+A(x2,y2)+ C∗(|x1−x2|+|y1−y2|)

                =A(x1,y1)+A(x2,y2) + C∗(x1 − x2) + C*(y2 − y1)

                =A(x1,y1) + C ∗ (x1 - y1) + A(x2,y2) + C*(y2 - x2)

分成兩部分,分別求最小值

【程式碼源】每日一題 國家鐵路

  同上,考慮列舉(x2,y2)的位置,滿足x1>=x2且y2>=y1關係的點在圖中陰影部分,快速求(x1,y1)的最佳位置,O(1)的複雜度查詢(x1,y1)的最佳位置呢?

二維字首最小值

二維字首和:矩形每個元素值的和。

二位字首最小值:矩形中每個元素值的最小值。

性質與二位字首和類似,但又有所不同,二維字首最小值不能求任意矩形區域的最小值,矩形的起點是固定的。此題剛好可以滿足要求,利用二維字首最小值的性質預處理在O(1)的複雜度下求出(x1,y1)的最佳位置。

那麼如何預處理二維字首最小值呢?

【程式碼源】每日一題 國家鐵路

 令f(i)(j)為以(1,1)為起點(i,j)為終點的矩形。

  f(x,y)可以有圖中紅色區域和黃色區域轉移而來,故而可得到轉移方程

                f (x , y) = min( f(x,y-1) ,f(x-1)(y),a(x)(y) )

 

詳見程式碼

#include <bits/stdc++.h>
using namespace std;
#define int long long //一定要開longlong,沒開longlong wa了三次,調半天的bug
int h, w, c, ans = 1e18;
int pre_min[1009][1009], pre_min2[1009][1009], a[1009][1009]; // pre_min為第一種情況下預處理的字首最小值,pre_min2為第二種情況
signed main()
{
    scanf("%lld%lld%lld", &h, &w, &c);
    for (int i = 1; i <= h; i++)
        for (int j = 1; j <= w; j++)
            scanf("%lld", &a[i][j]); //輸入
    //情況一
    for (int i = 0; i <= h; i++)
        pre_min[i][0] = 1e18; //初始化!一定要開比較大,因為(i+j)*c最大可以達到2000*1e9!!!
    for (int i = 0; i <= w; i++)
        pre_min[0][i] = 1e18;
    for (int i = 1; i <= h; i++)
        for (int j = 1; j <= w; j++)
            pre_min[i][j] = min(pre_min[i - 1][j], min(pre_min[i][j - 1], a[i][j] - c * (i + j))); //二維字首最小值
    for (int i = 1; i <= h; i++) //列舉(x2,y2)
        for (int j = 1; j <= w; j++)
            ans = min(ans, a[i][j] + c * (i + j) + min(pre_min[i - 1][j], pre_min[i][j - 1])); //更新最小值
    //情況二
    for (int i = 0; i <= h; i++) //初始化
        pre_min2[i][0] = 1e18;
    for (int i = 0; i <= w; i++)
        pre_min2[h + 1][i] = 1e18; //這裡有所不一樣,二位字首最小值的起點為(h,1)
    for (int i = h; i >= 1; i--) //列舉(x2,y2)
        for (int j = 1; j <= w; j++)
            pre_min2[i][j] = min(pre_min2[i + 1][j], min(pre_min2[i][j - 1], a[i][j] + c * (i - j))); //二維字首最小值
    for (int i = 1; i <= h; i++)
        for (int j = 1; j <= w; j++)
            ans = min(ans, a[i][j] + c * (j - i) + min(pre_min2[i + 1][j], pre_min2[i][j - 1])); //更新最小值
    cout << ans;
}
【程式碼源】每日一題 國家鐵路


相關文章