【题目链接】
https://codeforces.com/contest/1739/problem/E
【题目大意】
有一个长廊,可以看成是2行N列的小格子组成的。有些小格子是脏的,现在我们使用机器人进行清扫。
机器人总是清扫离它最近的那个小格子。然后继续清扫那些还是脏的小格子。机器人从(1,1)开始进行清扫。*(长廊左上角是(1,1),右下角是(2,N),括号是(行数,列数))。
但是这个机器人有个Bug,当同时有几个点离他的距离是一样近的时候,它就会死机。我们现在有两个点分别是
(
x
1
,
y
1
)
(
x
2
,
y
2
)
(x_1,y_1)(x_2,y_2)
(x1,y1)(x2,y2)距离计算公式是
∣
x
1
−
x
2
∣
+
∣
y
1
−
y
2
∣
|x_{1}-x_{2}|+|y_{1}-y_{2}|
∣x1−x2∣+∣y1−y2∣。当机器人和这其中一些点距离一样,我们需要事先(启动机器人前)取出这些点。
现在,我们求,在机器人不会死机的状态下。机器人最多可以清扫多少点,输出。
【题目详解】
首先,我们来看看一种情况。
这张图我们有两个脏点,绿色机器人是我们开始启动机器人的点。
我们发现,事实上,你可以移动这个绿色的机器人,比如移动到红色。从绿色出发的小机器人,和从红色出发的小机器人,获得的答案是一样的,为1。因为图中(1)(2)两个点是冲突的。
现在,我们来看这种情况:
绿色小机器人可以相当于从黄色出发吗?不可以。因为从黄色出发,那么对于(1)(2)两个脏节点的,距离,就被改变了。
所以,我们想到了动态规划。
首先我们有两个行,上面那个行用0表示,下面那个行用1表示。现在绿色机器人在j列。
定义dp[i][j]
意思是,机器人在(i,j)位置出发,最大能清扫的灰尘数量。并且(i,j)位置地下,我们默认下面没有灰尘。(就是题目的那个问题啦)
那么dp[i][j]
现在的想法是dp[i][j]=ch[i][j+1]+dp[i][j+1]
(就是上图这种)
ch[行][列]
是我们读入的灰尘数据,因为dp[i][j]
本身不包含(i,j)自己的脏灰,所以我们认为,我们先去(i,j)吃掉脏灰(如果有的话)。在从这里出发,即dp[i][j]
。
让我们继续往下想之前,先看看这个样例:
8
00001101
01000110
它的输出应该是5,而不是4。
我们拿掉的不是因为第二行,最先遇到的1冲突,而是直接把它拿掉。
即现在地图变为:
8
00001101
00000110
可见,就算不冲突,也应该,拿掉这个,获得最大的机器人清扫值。
现在,我们来按照“题目要求”,想想。假设,我们已经计算完了dp[*][j+1]
(从j+1列到n列,所有可能出发到n列,满足题意的最大清扫灰尘数目,也就是不会出现故障的清扫法)
那么,下图红色数字代表,从这里出发,最大能吃的灰尘。一个是4,一个是3,先自己勾画勾画感受一下。
绿色机器人,只能选择4。我们上文说过了,机器人横向移动。
如果小机器人下面有个1(脏灰)呢?
即:
那么你有两种方法(注意,我们说过了,小机器所在的位置,默认就是没有脏灰),第一种,把下面的那个(5)号脏灰拿掉,那么就算dp[0][j+1]
状态。要不我不拿掉,就从(5)开始吃,那么就是,先吃五,然后再看dp[1][j+1]
的状态。
所以,如果(i,j)对面有1的话,(1)要么事先拿走对面的1,(2)要么考虑对面的1。
仔细想想,对于(2)依旧有一些限制。
如果是这样,那么(1,j)和(0,j+1)只能二选一了。如果选择了(1,j)的话,直接使用dp[1][j+1]
是肯定不对的。因为有可能它包含了,(0,j+1)这个脏点,我们dp可没有保证,一定不会吃它上面的脏灰。所以我们不从dp[1][j+1]
走,而从dp[1][j+2]
走,这样,j+2列,肯定不会包含j+1列,怎么吃的方法。(我们的dp定义,从j这个列,一直到n,j+1不包含),对于j+1列,我们绝对不考虑(0,j+1)这个元素内的脏灰,就算有,也应该提前被手动拿走,这样才符合题意。然后从单独考虑(1,j+1)有没有脏灰,Over,这下正确了。
对于每一个脏灰,动态规划都有可能考虑到取用和不取用,所以这样的想法符合题意,应该是正确的。
给出如果对面有脏灰的转移方程:dp[j][i] = max(dp[j][i + 1] + (ch[j][i + 1] - '0'), 1 + (ch[j ^ 1][i + 1] - '0') + dp[j ^ 1][i + 2] + (ch[j ^ 1][i + 2] - '0'));
这样,我们有了完整的解题思路,注意开始和边界,写代码吧!
参考代码:
#include <bits/stdc++.h>
#include <iostream>
using namespace std;
int n;
#define Max_N 200000
char ch[3][Max_N + 5];
int dp[3][Max_N + 5];
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 0; i < 2; i++) {
for (int j = 1; j <= n; j++) {
cin >> ch[i][j];
}
}
ch[0][n + 1] = ch[0][n + 2] = ch[1][n + 1] = ch[1][n + 2] = '0';
for (int i = n; i >= 1; i--) {
for (int j = 0; j < 2; j++) {
if (ch[j ^ 1][i] == '0') { //对面没有脏东西,没啥好比较的
dp[j][i] = dp[j][i + 1] + (ch[j][i + 1] - '0');
}
if (ch[j ^ 1][i] == '1') { //对面有脏东西,先前拿走和不拿走都试一下(就是题目中的拿走)
dp[j][i] = max(dp[j][i + 1] + (ch[j][i + 1] - '0'), 1 + (ch[j ^ 1][i + 1] - '0') + dp[j ^ 1][i + 2] + (ch[j ^ 1][i + 2] - '0'));
}
}
}
cout << dp[0][1];
return 0;
}
参考代码2:
#include <bits/stdc++.h>
using namespace std;
int dp[200005][2][2]; //列 行
int dp2[200005][2];
string s[2];
int n;
int dpp(int p, int q, int r) { // p是列,q是行
int &ans = dp[p][q][r]; //对于一个处于(p,q)处的机器人,它可以向右清洁,也可以向下清洁。
if (p == n) ans = 0;
if (~ans) return dp[p][q][r] = ans;
int id = s[q][p] - '0';
ans = id + dpp(p + 1, q, 0); //向右清洁一定是可以的,如果下面有1需要清洁,这一步的含义就是,我们把下面的1拿掉了,解决冲突。
if (s[1 - q][p] == '1' && !r) // r=1是特殊状态,意味着,对面一定是没有灰尘的所以不进行这步,哪怕对面确实有灰尘
ans = max(id + 1 + dpp(p + 1, 1 - q, 1), ans); //这一步的含义是把右边的1拿掉了,下面的1保留着,看看这样的情况是什么答案。
return dp[p][q][r] = ans;
}
int dpp2(int p, int q) { // p列 q行 dp是[列][行] s[行][列]
int &ans = dp2[p][q];
if (p >= n) ans = 0;
if (~ans) return dp2[p][q] = ans;
int id = s[q][p] - '0';
ans = id + dpp2(p + 1, q);
if (s[1 - q][p] == '1') { //考虑p+2列的状态即可
ans = max(id + 1 + (s[1 - q][p + 1] - '0') + dpp2(p + 2, 1 - q), ans);
}
return dp2[p][q] = ans;
}
int main() {
memset(dp, -1, sizeof(dp));
memset(dp2, -1, sizeof(dp2));
scanf("%d", &n);
cin >> s[0] >> s[1];
s[0] += "00", s[1] += "00";
printf("%d", dpp2(0, 0));
return 0;
}
其中dpp和dpp2都是正确的,只是,两个不同的想法。即:对于冲突,我们怎么转移。Over。
————
喜欢请给我一个赞赞,谢谢。