[Daimayuan] 国家铁路(C++,数学,动态规划)

题目描述

d l s dls dls的算竞王国可以被表示为一个有 H H H行和 W W W列的网格,我们让 ( i , j ) (i,j) (i,j)表示从北边第 i i i行和从西边第 j j j列的网格。最近此王国的公民希望国王能够修建一条铁路。

铁路的修建分为两个阶段:

  1. 从所有网格中挑选 2 2 2个不同的网格,在这两个网格上分别修建一个火车站。在一个网络上修建一个火车站的代价是 A i , j A_{i,j} Ai,j
  2. 在这两个网格间修建一条铁轨,假设我们选择的网格是 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2),其代价是 C × ( ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ ) C×(|x_1−x_2|+|y_1−y_2|) C×(x1x2+y1y2)

d l s dls dls的愿望是希望用最少的花费去修建一条铁路造福公民们。现在请你求出这个最小花费。

题目输入

第一行输入三个整数分别代表 H , W , C ( 2 ≤ H , W ≤ 1000 , 1 ≤ C ≤ 1 0 9 ) H,W,C(2≤H,W≤1000,1≤C≤10^9) H,W,C(2H,W1000,1C109)

接下来 H H H行,每行 W W W个整数,代表 A i , j ( 1 ≤ A i , j ≤ 1 0 9 ) A_{i,j}(1≤A_{i,j}≤10^9) Ai,j(1Ai,j109)

题目输出

输出一个整数代表最小花费。

样例输入1
3 4 2
1 7 7 9
9 6 3 7
7 8 6 4
样例输出1
10
样例输入2
3 3 1000000000
1000000 1000000 1
1000000 1000000 1000000
1 1000000 1000000
样例输出2
1001000001
解题思路

这道题有亿点点难QAQ。

直接枚举的时间复杂度为 o ( n 4 ) o(n^4) o(n4),直接 T T T飞,所以我们需要想办法优化。

题中给出,修建一条铁轨的总花费为 A x 1 , y 1 + A x 2 , y 2 + C ∗ ( ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ ) A_{x_1,y_1}+A_{x_2,y_2}+C*(|x_1-x_2|+|y_1-y_2|) Ax1,y1+Ax2,y2+C(x1x2+y1y2)

显然,交换一下 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2)的坐标对花费没有任何影响。

所以,为了方便计算,我们规定情况 1 1 1 x 1 ≥ x 2 , y 1 ≥ y 2 x_1\ge x_2,y_1\ge y_2 x1x2,y1y2,情况 2 2 2 x 1 ≥ x 2 , y 1 ≤ y 2 x_1\ge x_2,y_1\le y_2 x1x2,y1y2

直观的来说,以上两种情况分别为矩阵的主对角线方向和副对角线方向。

只需要讨论一种情况的算法,另外一种情况则不证自明:

对于情况 1 1 1,总花费可以写为:
c o s t _ s u m = A x 1 , y 1 + A x 2 , y 2 + C ∗ ( x 1 − x 2 + y 1 − y 2 ) = A x 1 , y 1 + C ∗ ( x 1 + y 1 ) + A x 2 , y 2 − C ∗ ( x 2 + y 2 ) \begin{aligned} cost\_sum & = A_{x_1,y_1}+A_{x_2,y_2}+C*(x_1-x_2+y_1-y_2) \\ & = A_{x_1,y_1}+C*(x_1+y_1)+A_{x_2,y_2}-C*(x_2+y_2) \end{aligned} cost_sum=Ax1,y1+Ax2,y2+C(x1x2+y1y2)=Ax1,y1+C(x1+y1)+Ax2,y2C(x2+y2)
在公式的形式简化之后,再去考虑寻找最小值的问题:

我们发现对于给定的 ( x 1 , y 1 ) (x_1,y_1) (x1,y1),我们需要枚举所有符合条件的 ( x 2 , y 2 ) (x_2,y_2) (x2,y2)(条件为 x 1 ≥ x 2 , y 1 ≥ y 2 x_1\ge x_2,y_1\ge y_2 x1x2,y1y2,在一个小矩阵中),并找出最小值。

但与之前不同,我们现在可以对 ( x 2 , y 2 ) (x_2,y_2) (x2,y2)进行动态规划,然后就可以在 O ( 1 ) O(1) O(1)时间内获取对于给定 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)的最小值。

规划公式为dp[i][j] = min{ dp[i][j-1], dp[i-1][j], station[i][j] - C * (i + j)}​

for (int i = 1; i <= h; i++) {
	for (int j = 1; j <= w; j++) {
		dp[i][j] = min(dp[i][j - 1], min(dp[i - 1][j], station[i][j] - C * (i + j)));
	}
}

那么计算主对角线情况下的最小值,只需要枚举另外一个点:

for (int i = 1; i <= h; i++) {
	for (int j = 1; j <= w; j++) {
		ans = min(ans, min(dp[i - 1][j] + dp[i][j - 1]) + station[i][j] + C * (i + j));
	}
}

最后简单给出计算副对角线情况下最小值的代码以及AC代码:

副对角线算法:

for (int i = 1; i <= h; i++) {
    for (int j = w; j >= 1; j--) {
        dp[i][j] = min(dp[i][j + 1], min(dp[i - 1][j], station[i][j] + C * (j - i)));
    }
}

for (int i = 1; i <= h; i++) {
    for (int j = 1; j <= w; j++) {
    	ans = min(ans, min(dp[i - 1][j], dp[i][j + 1]) + station[i][j] + C * (i - j));
	}
}

AC代码:

#include <iostream>
#include <string.h>
using namespace std;
const long long max_h = 1000;
const long long max_w = 1000;
const long long NaN = 0x3F3F3F3F3F3F3F3F;
const long long max_c = 1e9;

long long station[max_h + 2][max_w + 2], dp1[max_h + 2][max_w + 2], dp2[max_h + 2][max_w + 2];	//多开一圈,防止越界
long long h, w, C;

int main() {
	cin >> h >> w >> C;
	for (int i = 1; i <= h; i++) {
		for (int j = 1; j <= w; j++) {
			cin >> station[i][j];
		}
	}

	//DP1(主对角线)
	memset(dp1, 0x3F, sizeof(long long) * (max_h + 2) * (max_w + 2));
	for (int i = 1; i <= h; i++) {
		for (int j = 1; j <= w; j++) {
			dp1[i][j] = min(dp1[i - 1][j], min(dp1[i][j - 1], station[i][j] - C * (i + j)));
		}
	}

	//DP2(副对角线)
	memset(dp2, 0x3F, sizeof(long long) * (max_h + 2) * (max_w + 2));
	for (int i = 1; i <= h; i++) {
		for (int j = w; j >= 1; j--) {
			dp2[i][j] = min(dp2[i][j + 1], min(dp2[i - 1][j], station[i][j] + C * (j - i)));
		}
	}

	long long ans = NaN;
	//ans1(主对角线)
	for (int i = 1; i <= h; i++) {
		for (int j = 1; j <= w; j++) {
			ans = min(ans, min(dp1[i - 1][j], dp1[i][j - 1]) + station[i][j] + C * (i + j));
		}
	}

	//ans2(副对角线)
	for (int i = 1; i <= h; i++) {
		for (int j = 1; j <= w; j++) {
			ans = min(ans, min(dp2[i - 1][j], dp2[i][j + 1]) + station[i][j] + C * (i - j));
		}
	}
	cout << ans << endl;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WitheredSakura_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值