CF1073C题解
在洛谷食用更佳
简化题意
已知一字符串 s s s 长度为 n n n,且 s s s 中各字符代表的意义如下。
- U U U 向上走一步;
- D D D 向下走一步;
- L L L 向左走一步;
- R R R 向右走一步;
现在你在平面直角坐标系的原点,你需要改变字符串 s s s 中的某个子串中的字符,使得最后能到 ( x , y ) (x,y) (x,y) 位置。求这个子串的最小长度。
思路
二分
首先,二分的要求:有界性、单调性、二段性。
本题中,我们设答案为 l l l(固定),则所有大于 l l l 的答案都可行,也就是说一定可以通过改变长度大于 l l l 的子串以到达目标点(比如你可以让多余的子串里选一些 R R R 改成 L L L,相同数量的 L L L 改成 R R R,相当于没改……)。
显然,本题满足二分的条件。
由于题解里面有很多二分的做法,所以在这里我就稍微带一下,代码就不传了。
时间复杂度 O ( n log n ) O(n\log{n}) O(nlogn)。
尺取
尺取的概念、模板题等详见【洛谷日报#73】尺取法小结。
这里我先简单说一下尺取的基本内容。
尺取的要求
-
取的是一段连续的区间(有两个端点);
-
结果不会因区间的变长而由成立变成不成立;
-
左端点右移,右端点不会往左移。
要求一是本题的条件,要求二前面已经证明过。所以本题满足尺取的条件。
方法
-
按照题意,算出实际上最后到达的位置,并设答案为 n + 1 n + 1 n+1;
-
特判不用改动就能到的情况;
-
设左端点为 l l l,右端点为 r r r,并赋初始值 1 1 1;
-
从左到右枚举每个右端点,被枚举到的点取消 s r s_r sr 的对应操作。如果区间长度大于等于当前位置与目标点的曼哈顿距离,就右移左端点;
-
对于每次右移左端点,如果当前区间长度和两点的曼哈顿距离同奇同偶,更新答案;
-
“回溯”,更新答案后,恢复 s l s_l sl 的操作;
-
若答案为 n + 1 n + 1 n+1,说明无解。
证明
为什么判断是否右移左端点只要看曼哈顿距离:类比一下“撤回”操作。取消 s r s_r sr 的操作后,可以修改成向任意方向走一步,此时相当于从 l − 1 l - 1 l−1 位置开始,看走 r − l + 1 r - l + 1 r−l+1 步能否到达目标点。设曼哈顿距离为 p p p,则:
-
当 p = r − l + 1 p = r - l + 1 p=r−l+1 时,正好能走到目标点;
-
当 p ≤ r − l + 1 p \leq r - l + 1 p≤r−l+1 且 p ≡ r − l + 1 ( m o d 2 ) p \equiv r - l + 1 \pmod 2 p≡r−l+1(mod2) 时,可以通过“一上一下,一左一右”走到目标点。
我们发现,有用的步数就是曼哈顿距离 p p p。多往某个方向走一步,就得多往相反的方向走一步,所以多余的步数一定是偶数。由于任何一个整数加偶数,奇偶性不变,所以当且仅当曼哈顿距离 p p p 和区间长度 r − l + 1 r - l + 1 r−l+1 同奇同偶时,修改后可以到达目标点。
时间复杂度 O ( n ) O(n) O(n)。
上代码!
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
char s[N];
int main()
{
int sx=0,sy=0;
int n;
scanf("%d",&n);
scanf("%s",s+1);
int fx,fy;
scanf("%d%d",&fx,&fy);
for(int i=1;i<=n;i++)
{
if(s[i]=='U')
++sy;
else if(s[i]=='D')
--sy;
else if(s[i]=='L')
--sx;
else
++sx;//计算最终位置 。
}
if(sx==fx&&sy==fy)
return 0*printf("0\n");//特判不用修改的情况 。
int l=1,minn=n+1;
for(int r=1;r<=n;++r)
{
if(s[r]=='U')
--sy;
else if(s[r]=='D')
++sy;
else if(s[r]=='L')
++sx;
else
--sx;//枚举右端点,取消s[r]操作 。
while(l<=r&&abs(fx-sx)+abs(fy-sy)<=r-l+1)
{
if(((abs(fx-sx)+abs(fy-sy)))%2==(r-l+1)%2)
minn=min(minn,r-l+1);
if(s[l]=='U')
++sy;
else if(s[l]=='D')
--sy;
else if(s[l]=='L')
--sx;
else
++sx;//枚举左端点,恢复s[l]操作。
++l;
}
}
printf("%d\n",minn==n+1?-1:minn);
return 0;
}
//陈文忻 2023-10-22。
//诚信做题,拒绝ctrl C+ctrl V。
讲得不是很详细,自己意会吧。