CF213C Relay Race【接力赛】 题解

本文介绍了如何使用动态规划解决洛谷CF213CRelayRace题目,通过理解题意、分析思路,将问题转化为同时移动的两个角色的最大价值和计算,最终通过哈曼顿距离优化算法降低时间复杂度。
摘要由CSDN通过智能技术生成


前言

此乃小 Oler 的一篇算法随笔,从今日后,还会进行详细的修订

Relay Race

题目

源自 洛谷 CF213C Relay Race

题面描述

福瑞克 (Furik) 和鲁比克 (Rubik) 参加接力赛。比赛将在一个大广场上举行,广场的一侧有 n n n 米。给定的正方形被划分为 n × n n \times n n×n 个单元格(表示为单位正方形),每个单元格都有一些数字。

比赛开始时, Furik 站在坐标为 ( 1 , 1 ) (1,1) (1,1) 的单元格中, Rubik 站在座标为 ( n , n ) (n,n) (n,n) 的单元中。在开始后, Furik 立即跑向 Rubik ,此外,如果 Furik 站在坐标为 ( i , j ) (i,j) (i,j) 的单元格处,则他可以移动到单元格 ( i + 1 , j ) (i+1,j) (i+1,j) ( i , j + 1 ) (i,j+1) (i,j+1) 。 Furik 到达 Rubik 后, Rubik 开始从坐标为 ( n , n ) (n,n) (n,n) 的单元格运行到坐标为 ( 1 , 1 ) (1,1) (1,1) 的单元格。如果 Rubik 站在单元格 ( i , j ) (i,j) (i,j) 中,那么他可以移动到单元格 ( i − 1 , j ) (i-1,j) (i1,j) ( i , j − 1 ) (i,j-1) (i,j1) 。 Furik 和 Rubik 都不允许超越领域的边界;如果一个球员越过边界,他将被取消比赛资格。

为了赢得比赛,福瑞克和鲁比克必须获得尽可能多的分数。点数是从 t t t 开始的数的总和。

输入格式

第一行包含单个整数 ( 1 ≤ n ≤ 300 ) (1 \le n \le 300) 1n300

接下来的 n n n 行各包含 n n n 整数:
i i i a i , j a_{i,j} ai,j 上的第 j j j 个数字 a i , j ( − 1000 ≤ a i , j ≤ 1000 ) a_{i,j}(-1000 \le a_{i,j} \le 1000) ai,j(1000ai,j1000) 是在坐标为 ( i , j ) (i,j) (i,j) 的单元格中写入的数字。

输出格式

在一行上打印一个数字——这就是问题的答案。

样例 #1

样例输入 #1
1
5

样例输出 #1

5

样例 #2

样例输入 #2
2
11 14
16 12
样例输出 #2
53

样例 #3

样例输入 #3
3
25 16 25
12 18 19
11 13 8
样例输出 #3
136

提示

对第二个样例的评论:
Furik 的最佳路径是: ( 1 , 1 ) (1,1) (1,1) , ( 1 , 2 ) (1,2) (1,2) , ( 2 , 2 ) (2,2) (2,2),而Rubik的最佳路径是: ( 2 , 2 ) (2,2) (2,2) , ( 2 , 1 ) (2,1) (2,1) , ( 1 , 1 ) (1,1) (1,1)
对第三个样例的评论:
Furik 的最佳路径为: ( 1 , 1 ) (1,1) (1,1) , ( 1 , 2 ) (1,2) (1,2) , ( 1 , 3 ) (1,3) (1,3) , ( 2 , 3 ) (2,3) (2,3) , ( 3 , 3 ) (3,3) (3,3) ,对于Rubik: ( 3 , 3 ) (3,3) (3,3) , ( 3 , 2 ) (3,2) (3,2) , ( 2 , 2 ) (2,2) (2,2) , ( 2 , 1 ) (2,1) (2,1) , ( 1 , 1 ) (1,1) (1,1)
样例的数字:


Furik 的路径用黄色标记, Rubik 的路径则用粉色标记

题解

理解题意

输入一个 n × n n \times n n×n矩形,每个 a i , j a_{i,j} ai,j 是这个位置的价值。现在要从左上角走到右下角返回,每个价值只被计算一次,求最大价值和

分析思路

看完题目会马上想到和本题非常相似的两题 P1006 [NOIP2008 提高组] 传纸条P1004 [NOIP2000 提高组] 方格取数
可是一看数据,麻了, n ≤ 300 n \le 300 n300 ,可是一时间想不到什么办法,只好先硬着头皮打暴力

我们可以把一来一回看作两个人同时从起点出发同时移动,所以可以定义 f x 1 , y 1 , x 2 , y 2 f_{x_1,y_1,x_2,y_2} fx1,y1,x2,y2 为当 Furik 走到 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) 的方格时, Rubik 走到 ( x 2 , y 2 ) (x_2,y_2) (x2,y2) 方格内的最大价值和
则当前状态为
f i , j , k , l ← a i , j + a k , l + max ⁡ { f i − 1 , j , k − 1 , l f i , j − 1 , k , l − 1 f i − 1 , j , k , l − 1 f i , j − 1 , k − 1 , l f_{i,j,k,l} \gets a_{i,j}+a_{k,l}+\max \begin{cases} f_{i-1,j,k-1,l}\\ f_{i,j-1,k,l-1}\\ f_{i-1,j,k,l-1}\\ f_{i,j-1,k-1,l} \end{cases} fi,j,k,lai,j+ak,l+max fi1,j,k1,lfi,j1,k,l1fi1,j,k,l1fi,j1,k1,l

若两个人走到同一个方格中,会计算重复,那我们应删去一个
即当 i = = k i==k i==k 并且 j = = l j==l j==l 同时成立时, f i , j , k , l − = a i , j f_{i,j,k,l}-=a_{i,j} fi,j,k,l=ai,j
最后取 f n , n , n , n f_{n,n,n,n} fn,n,n,n 就是答案了。

但由于 n n n 可能达到 300 300 300 ,时间复杂度为 O ( n 4 ) O(n^4) O(n4) ,所以会出现爆空间和内存

Code ( MLE+TLE , 40ps )

#include<bits/stdc++.h>
using namespace std;
const int N=101;
int n,x,y,z,a[N][N];
int f[N][N][N][N]; 
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++)
			scanf("%d",&a[i][j]);
	}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++) {
			for(int k=1;k<=n;k++) {
				for(int l=1;l<=n;l++) {
					f[i][j][k][l]=max(f[i-1][j][k-1][l],max(f[i][j-1][k][l-1],max(f[i-1][j][k][l-1],f[i][j-1][k-1][l])))+a[i][j]+a[k][l];
					if(k==i&&l==j) f[i][j][k][l]-=a[i][j];
				}
			}
		}
	}
	printf("%d\n",f[n][n][n][n]);
	return 0;
}

经过一番思考,可以想到,在定义dp数组 f f f 时, Furik 和 Rubik 是同时出发的,所以他们两个人所走的步数也一样是相同的,再深入则引出了著名的哈曼顿距离,简单来说就是对于任意一个 ( x , y ) (x,y) (x,y) 方格,这个距离就为 x + y x+y x+y , 即某个点或方格的横纵坐标的和

那么可以优化定义 f i , x 1 . x 2 f_{i,x_1.x_2} fi,x1.x2 ,表示当两人走了 i i i 步时, Furik 的横坐标 x 1 x_1 x1 , Rubik 的横坐标 x 2 x_2 x2 ,知道了横坐标,必然不能少了纵坐标

由刚刚的定理易得两条性质

  • { y 1 = i − x 1 y 2 = i − x 2 \begin{cases} y_1=i-x_1\\ y_2=i-x_2 \end{cases} {y1=ix1y2=ix2
  • x 1 = x 2 x_1=x_2 x1=x2 ,那么 y 1 = y 2 y_1=y_2 y1=y2

状态转移方程式
f i , j , k = a i − j , j + a i − k , k + max ⁡ { f i − 1 , j , k f i − 1 , j − 1 , k f i − 1 , j , k − 1 f i − 1 , j − 1 , k − 1 f_{i,j,k} = a_{i-j,j}+a_{i-k,k}+\max \begin{cases} f_{i-1,j,k}\\ f_{i-1,j-1,k}\\ f_{i-1,j,k-1}\\ f_{i-1,j-1,k-1} \end{cases} fi,j,k=aij,j+aik,k+max fi1,j,kfi1,j1,kfi1,j,k1fi1,j1,k1

同样的我们也要消掉两人走到同一个方格时的价值

时间复杂度 O ( 2 × n 3 ) O(2 \times n^3) O(2×n3) ,并不会 TLE。

Code2 ( AC , 100ps )

#include<bits/stdc++.h>
using namespace std;
const int oo=0x3f3f3f3f;
const int N=301;
int n,a[N][N];
int f[N<<1][N][N];
int main() {
	scanf("%d",&n);
    for(int i=1;i<=n;i++) {
    	for(int j=1;j<=n;j++)
			scanf("%d",&a[i][j]);
	}
    memset(f,-oo,sizeof(f));     //存在负数,所以初始化设成-oo
	f[2][1][1]=a[1][1];
	for(int i=3;i<=(n<<1);i++) {       //枚举步数
		for(int j=1;j<=n&&j<i;j++) {      //Furik的横坐标
		    for(int k=j;k<=n&&k<i;k++) {     //Rubik的横坐标
		    	int val=a[i-j][j]; 
		        if(j!=k) val+=a[i-k][k];    //若不在同一个格子内,加上
		        f[i][j][k]=max(f[i][j][k],f[i-1][j][k]+val);
		        f[i][j][k]=max(f[i][j][k],f[i-1][j-1][k]+val);
		        f[i][j][k]=max(f[i][j][k],f[i-1][j][k-1]+val);
		        f[i][j][k]=max(f[i][j][k],f[i-1][j-1][k-1]+val);
			}
		}
	}
    printf("%d\n",f[n<<1][n][n]);
    return 0;
}

后记

此处为本人的错误小笔记,与原文无关。

1) 还原

一开始的思路是按照题目说的去做,先计算 Furik 的最优路径 $f_{n,n},并用 x , y x,y x,y 数组记录路径,然后再把最优路径上的方格都用 v i s vis vis 标记其访问过(也可修改其值为 0 0 0 ),然后再从 ( n , n ) (n,n) (n,n) 开始,找出 Rubik 到 ( 1 , 1 ) (1,1) (1,1)最大值,存入数组 g g g 中,最后直接把两个最优路径相加 f n , n + g 1 , 1 f_{n,n}+g_{1,1} fn,n+g1,1 完成。

2) 推翻

由于定义的数组 f , g f,g f,g 都是基于当前的最优路径价值,并不能直接将其相加使之成为全局的最大价值和。

由于两条路径可能出现重叠的部分,而在寻找第一个最优路径时,做法舍弃了其他方格上的数值,把本来只需用一次访问的方格重复访问了两次,浪费了步数,从而导致程序算不出最优解

WA 程序答案路径:
在这里插入图片描述
正解的路径:
在这里插入图片描述

Code3 ( WA , 40ps )

浅浅记录一下错误代码…

#include<bits/stdc++.h>
using namespace std;
const int oo=0x3f3f3f3f;
const int N=520;
int n,a[N][N],f[N][N];
int g[N][N],x[N][N],y[N][N];
bool vis[N][N];
void dfs(int xx,int yy) {
	if(!xx&&!yy) return ;
	vis[xx][yy]=1;
	dfs(x[xx][yy],y[xx][yy]); 
}
int main() {
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++) {
			scanf("%d",&a[i][j]);
			f[i][j]=g[i][j]=-oo;
		}
	}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++) {
			if(f[i][j-1]+a[i][j]>f[i][j]) {
				f[i][j]=f[i][j-1]+a[i][j];
				x[i][j]=i;
				y[i][j]=j-1;
			}
			if(f[i-1][j]+a[i][j]>f[i][j]) {
				f[i][j]=f[i-1][j]+a[i][j];
				x[i][j]=i-1;
				y[i][j]=j;
			}
		}
	}
	dfs(n,n);
	for(int i=n;i>=1;i--) {
		for(int j=n;j>=1;j--) {
			g[i][j]=max(g[i+1][j],max(g[i][j+1],g[i][j]));
			if(!vis[i][j])
				g[i][j]+=a[i][j];
		}
	} 
	printf("%d\n",f[n][n]+g[1][1]);
	return 0;
} 

C语言实现将C语言实现将F0AC66083500103166000000000835001031660000000099990000000000400200006400000000000000000000000000CE7BE622DEB9DABA217088D8047DBDA65295C09D171BF60888A1209AB0DD38435F3BDF273CA388E1BB8B3C1D213C975E0ABCFF0101C6D24E8D949BD932552387425086738000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0835001031660000000000000000000000000000083500103166000000000000000000000000000099990000000002FAFFFFFFFFFFFF05FAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7A1729FEAA8A3FE69E5C83D178F80910E36A0089FA6D2437300D2360410761B914D623A5C6FCA34974634E这个长度为2032的字符串以每两个字符按英文逗号分隔,并且每两个字符加上(byte)0x前缀,并将结果输出到data.txt文件中个长度为2032的字符串以每两个字符按英文逗号分隔,并且每两个字符加上(byte)0x前缀,并将结果输出到data.txt文件中
06-08
好的,以下是将长度为2032的字符串以每两个字符按英文逗号分隔,并且每两个字符加上(byte)0x前缀,并将结果输出到data.txt文件中的C语言代码: ```c #include <stdio.h> #include <string.h> int main() { char s[2033] = "F0AC66083500103166000000000835001031660000000099990000000000400200006400000000000000000000000000CE7BE622DEB9DABA217088D8047DBDA65295C09D171BF60888A1209AB0DD38435F3BDF273CA388E1BB8B3C1D213C975E0ABCFF0101C6D24E8D949BD932552387425086738000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7A1729FEAA8A3FE69E5C83D178F80910E36A0089FA6D2437300D2360410761B914D623A5C6FCA34974634E"; // 定义原始字符串 int i; // 将字符串每两个字符以英文逗号分隔并加上(byte)0x前缀 char s2[4065]; for (i = 0; i < 2032; i += 2) { sprintf(s2 + i * 5 / 2, "(byte)0x%c%c, ", s[i], s[i+1]); } // 将结果输出到data.txt文件中 FILE *fp = fopen("data.txt", "w"); if (fp != NULL) { fputs(s2, fp); fclose(fp); } return 0; } ``` 以上代码利用字符串拼接和sprintf函数将字符串每两个字符以英文逗号分隔并加上(byte)0x前缀,最后将结果输出到data.txt文件中。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值