题解 P1081 [NOIP2012 提高组] 开车旅行
传送门。
题意
显然。
分析
先分析我们的询问。
对于第一个问题,我们可以枚举起点,计算出小 A 的路程总数,小 B 的路程总数,将比值比较即可求到答案、
对于第二个问题,就是某个起点下,最长为 $x$ 的小 A 路程总数,小 B 的路程总数。
因此,我们其实需要的操作只有一个:在某个起点,长度为 $x$ 的小 A 路程总数,小 B 的路程总数。
并且,得到这个操作的时间复杂度应当是 $O(\log n)$ 级别的。
先想一下暴力应当如何打。
首先,我们用 $O(n^2)$ 的时间复杂度处理出当前节点的最近的节点与次近的节点,然后在每一个操作时,暴力向后跳动,以 $O(n)$ 的时间复杂度解决每一个操作。
inline void init() {
for(int i=1; i<=n; ++i) {
nxt[i][0]=nxt[i][1]=n+1;
for(int j=i+1; j<=n; ++j) {
if(dis(i,j)<dis(i,nxt[i][1])) nxt[i][1]=j;
if(dis(i,j)==dis(i,nxt[i][1])&&h[nxt[i][1]]>h[j]) nxt[i][1]=j;
if(dis(i,nxt[i][0])>dis(i,nxt[i][1])) swap(nxt[i][0],nxt[i][1]);
if(dis(i,nxt[i][0])==dis(i,nxt[i][1])&&h[nxt[i][0]]>h[nxt[i][1]]) swap(nxt[i][0],nxt[i][1]);
}
swap(nxt[i][0],nxt[i][1]);
}
}
int tot[2];
inline node solve(int now,int mx) {
int flag=0;
tot[1]=tot[0]=0;
while(now<=n) {
if(nxt[now][flag]>n) break;
if(tot[1]+tot[0]+dis(nxt[now][flag],now)>mx) break;
tot[flag]+=dis(nxt[now][flag],now);
now=nxt[now][flag];
flag=!flag;
}
return <%tot[0],tot[1]%>;
}
总时间复杂度:$O(n\times m)$。拿下 $70pts$。
接着分析正解。
此时,我们的两个部分都是超出我们的期望时间复杂度的,因此,我们需要分开优化。
一方面,优化预处理。
为了处理出最近的与次近的,我们使用 set 来处理。
因为我们当前节点的最近与次近实则只有当前剩余的数字距离当前节点的最近的几个之一。
因此,实则,我们在处理时只需要寻找在数字上与其相邻的四个节点即可。
此部分时间复杂度:$O(n\log n)$。
再优化询问的时间复杂度,可以发现,我们的暴力是一步一步跳跃的,尽管是有两种跳跃,但是我们不妨令两个合并成一个操作。
最后就可以用倍增来解决。
时间复杂度:$O(n\times \log n)$。

浙公网安备 33010602011771号