题目链接:传送门
mmp 这题真的毒瘤 我比赛时候分的16种情况都没问题 死在边界上
题目大意:
有一个
n
∗
m
n*m
n∗m的地图,上面有
k
k
k个宝藏,有
q
q
q个安全列。现在你要用最小的步数收集所有的宝藏。要求:
1.
1.
1.不能往下走。
2.
2.
2.可以任意往左或往右走,但仅当在安全列上时才能往上走。
范围:
2
<
=
n
,
m
,
k
<
=
2
∗
1
0
5
,
q
<
=
m
2<=n,m,k<=2*10^5,q<=m
2<=n,m,k<=2∗105,q<=m
解题方法
由于不能往下走,所以一定是拿走每行的宝藏之后才能往上。
因此确定策略:
向左走收集完左边的宝藏,再向右走收集完右边的宝藏
或
向右走收集完右边的宝藏,再向左走收集完左边的宝藏
然后直接去一个安全列往上走。
不难发现,这样是没有后效性的,即珂以dp。
然后大力dp的复杂度好像是
O
(
n
q
2
)
O(nq^2)
O(nq2)?显然过不了。
然后发现好像珂以单调队列优化,去掉一个
O
(
q
)
O(q)
O(q)的复杂度,然而还是过不了QAQ。
于是手造几个图,发现到了最左边或最右边的宝藏后,只需要选择最近的一个安全列往上走就珂以了!
证明也比较显然,以走到最左边的宝藏且已经收集完这一行的所有宝藏为例:
令当前所在的行为
i
i
i,当前所在的列为
m
i
n
l
[
i
]
minl[i]
minl[i],当前所在的列左边第一个安全列编号为
p
o
s
pos
pos(即这一列为
b
[
p
o
s
]
b[pos]
b[pos]),那么当前所在的列左边第二个安全列编号为
p
o
s
−
1
pos-1
pos−1(即这一列为
b
[
p
o
s
−
1
]
b[pos-1]
b[pos−1])。
如果从
m
i
n
l
[
i
]
minl[i]
minl[i]走到
b
[
p
o
s
−
1
]
b[pos-1]
b[pos−1]再往上走,那么肯定不如走到
b
[
p
o
s
]
b[pos]
b[pos]再往上走优。
原因:从
m
i
n
l
[
i
]
minl[i]
minl[i]走到
b
[
p
o
s
−
1
]
b[pos-1]
b[pos−1]再往上走,与从
m
i
n
l
[
i
]
minl[i]
minl[i]走到
b
[
p
o
s
]
b[pos]
b[pos]再往上走,再往左走到
b
[
p
o
s
−
1
]
b[pos-1]
b[pos−1]列,步数相同。
因为第
i
i
i列已经无法收集到宝藏,而走到
b
[
p
o
s
]
b[pos]
b[pos]再往上走再往左走到
b
[
p
o
s
−
1
]
b[pos-1]
b[pos−1]列的走法有可能收集到上面一行的宝藏。
如果上面一行没有宝藏在第
b
[
p
o
s
]
b[pos]
b[pos]列右端,那么走到
b
[
p
o
s
]
b[pos]
b[pos]再往上走的走法是要往右多走一截的(这里最好画一些图帮助理解)。
如果上面一行有宝藏在
b
[
p
o
s
]
b[pos]
b[pos]右端,也不难发现走到
b
[
p
o
s
−
1
]
b[pos-1]
b[pos−1]再往上走的走法不会比走到
b
[
p
o
s
]
b[pos]
b[pos]再往上走的的走法更优。
因此选左边最近的安全列上去,是比选左边更远的安全列上去更优的。
右边同理。
于是策略变为:
每行走到最左端的宝藏处或者最右端的宝藏出,然后选左边最近的安全列和右边最近的安全列往上走。(这里左边和右边都要枚举一下)
然后就可以大力转移了qwq
时间复杂度: O ( n l o g q × 大 常 数 ) O(nlogq×大常数) O(nlogq×大常数)( O ( n ) O(n) O(n)扫,套上 O ( l o g q ) O(logq) O(logq)的二分找左边/右边最近的安全列)
另外:
比赛完我才发现,只要分8种情况就珂以了QAQ
现在很好奇我是怎么在比赛时16种情况一个不写错然后写错边界的qwq
8种情况(X
=
=
= 左/右):
从X边走X边最近的安全列走到上面一行最X边的的宝藏再走到另一边的宝藏。
一共
2
∗
2
∗
2
=
8
2*2*2=8
2∗2∗2=8(种)情况qwq。
代码
注:前方高能!
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#include<vector>
#define re register int
#define rl register ll
using namespace std;
typedef long long ll;
ll read() {
rl x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=10*x+ch-'0';
ch=getchar();
}
return x*f;
}
namespace I_Love {
const int Size=200005;
ll n,m,k,q,b[Size];
struct Treasure {
ll r,c;
} w[Size];
inline bool comp(Treasure x,Treasure y) {
if(x.r!=y.r) return x.r<y.r;
return x.c<y.c;
}
ll maxr[Size],minl[Size],sum[Size],up;
void Fujibayashi_Ryou() {
// b[1]=1; b[2]=3; b[3]=6; b[4]=7;
// printf("%d",b[upper_bound(b+1,b+1+4,5)-b-1]);
n=read();
m=read();
k=read();
q=read();
for(re i=1; i<=k; i++) {
w[i].r=read();
w[i].c=read();
}
for(re i=1; i<=q; i++) {
b[i]=read();
}
sort(b+1,b+1+q);
sort(w+1,w+1+k,comp);
b[q+1]=1e9;
int now=1;
//maxr表示一行最右边的宝藏的位置 minl表示一行最左边宝藏的位置
for(re i=1; i<=n; i++) {
while(now<=k && w[now].r<i) now++;
if(now>k || w[now].r>i) {
//后面转移时,如果有一行没有宝藏,就相当于把这行删掉,然后答案+1
maxr[i]=maxr[i-1];
minl[i]=minl[i-1];
} else {
//sum[i]表示第i行有几个宝藏
//up表示有宝藏的行中最上面的一行
minl[i]=w[now].c;
sum[i]=1;
up=i;
while(now<k && w[now+1].r==i) {
now++;
sum[i]++;
}
maxr[i]=w[now].c;
}
}
//先统计第一行的答案
ll prel=maxr[1]-1+(maxr[1]-minl[1]),prer=maxr[1]-1;
ll ansl=9e18,ansr=9e18;
//毒瘤 这里走到最上面有宝藏的一行就珂以了
for(re i=2; i<=up; i++) {
if(!sum[i]) {
prel++;
prer++;
continue;
} else {
int pos=upper_bound(b+1,b+1+q,minl[i-1])-b-1;
if(pos) {
//上去,到右边再到左边
ansl=min(ansl,prel+minl[i-1]-b[pos]+1+abs(b[pos]-maxr[i])+(maxr[i]-minl[i]));
//上去,到左边再到右边
ansr=min(ansr,prel+minl[i-1]-b[pos]+1+abs(b[pos]-minl[i])+(maxr[i]-minl[i]));
}
if(b[++pos]<=m) {
//上去,到右边再到左边
ansl=min(ansl,prel+b[pos]-minl[i-1]+1+abs(b[pos]-maxr[i])+(maxr[i]-minl[i]));
//上去,到左边再到右边
ansr=min(ansr,prel+b[pos]-minl[i-1]+1+abs(b[pos]-minl[i])+(maxr[i]-minl[i]));
}
pos=upper_bound(b+1,b+1+q,maxr[i-1])-b-1;
if(pos) {
//上去,到右边再到左边
ansl=min(ansl,prer+maxr[i-1]-b[pos]+1+abs(maxr[i]-b[pos])+(maxr[i]-minl[i]));
//上去,到左边再到右边
ansr=min(ansr,prer+maxr[i-1]-b[pos]+1+abs(minl[i]-b[pos])+(maxr[i]-minl[i]));
}
if(b[++pos]<=m) {
ansl=min(ansl,prer+b[pos]-maxr[i-1]+1+abs(maxr[i]-b[pos])+(maxr[i]-minl[i]));
ansr=min(ansr,prer+b[pos]-maxr[i-1]+1+abs(b[pos]-minl[i])+(maxr[i]-minl[i]));
}
}
prel=ansl,prer=ansr;
// printf("lans=%lld rans=%lld\n",ansl,ansr);
ansl=ansr=9e18;
}
printf("%I64d",min(prel,prer));
}
}
int main() {
I_Love::Fujibayashi_Ryou();
return 0;
}