[UOJ693] 地铁规划
这是一道交互题。
新首都跳蚤利亚需要建立地铁线路!hehe 蚤负责了这个项目。
跳蚤利亚有 $n$ 个地铁站,还有 $m$ 条线路计划设立,第 $i$ 条铁轨将在 $u_i$ 和 $v_i$ 之间建立一条双向线路($u_i\neq v_i$)。可能有两条线路连接的地铁站相同。
由于跳蚤利亚是面向未来的节能环保城市,hehe 蚤希望地铁的建设也要避免浪费。hehe 蚤仔细地审查工程建设方案,并定义了什么叫做“绿色区间”:对于任何满足 $1 \le l \le r \le m$ 的整数 $l, r$,我们称 $[l, r]$ 是一个区间;如果在只修建所有 $u_i, v_i \in [l, r]$ 的线路的情况下,地铁网络不包含环,那么我们称这个区间是绿色的。
hehe 蚤突然好奇:有多少个区间 $[l, r]$ 是绿色的呢?但这样的好奇持续了 $10^{-9}$ 秒后, hehe 蚤就想到了做法。如果一个区间是绿色的,那它的子区间也是绿色的。于是可以用双指针,对于所有的区间左端点 $l\in[1,m]$,计算出使得 $[l, r]$ 是绿色的最大的右端点值 $r$ 即可。而维护是否有环这一信息,只需使用跳蚤国最新自研数据结构 hehe 树。
作为跳蚤国顶级科学家之一,hehe 蚤刚在大量系统中植入了 hehe 树,所以这问题已经变得没意思了。为了挑战自我,hehe 蚤拿出了一台植入 hehe 树失败的机器,仅包含部分 hehe 树功能。具体来说,这台机器支持:
给定 $id$,在图中建立双向线路 $u_{id}, v_{id}$,注意,如果假如建立后图中产生了环,机器会报错。
(最多lim 次)拆除图中加入时间最晚的一条双向线路。
给定 $id$,询问 $u_{id}, v_{id}$ 两点间是否可以通过已经建立的双向线路连通。
拿出了这台机器后,hehe 蚤又得出了一个基于分治的强大的做法,并给出了答案。hehe 蚤大呼简单。
但 hehe 蚤又想了想,觉得自己应该在跳蚤国内弘扬这股 “麻烦自己,麻烦别人” 的挑战精神。于是 hehe 蚤把这台机器交给了你。他会从小到大依次对每个左端点 $l\in [1,m]$,向你询问,所有以 $l$ 为左端点的绿色区间 $[l, r]$ 中最大的右端点值 $r$。因为 hehe 蚤觉得这个问题太简单,所以在你计算答案 $r$ 时,你不可以让机器加入编号大于 $r$ 的线路。
一句话题意:有一个长度为 $m$ 的边序列,你需要使用交互库提供的可撤销并查集接口,在线地对于每个 $l$ 求出最大的 $r$,使得区间 $[l,r]$ 内的边不形成环。
\(n\le 10^5,lim\ge 5\times 10^6\)
好题。
先说说 Baka tricks。
如果你现在有个问题,需要双指针,但是维护的信息并不支持删除操作。此时你可以使用重构的方式。维护一个中点 \(p\),那么当左端点 \(l\) 超过 \(p\) 的时候,你可以把 \(p\) 重设在 \(r\) 处,然后对于中点 \(p\) 可以预处理出 \(\forall l\le i\le p\), \([i,p]\) 的区间答案以及 \(\forall p\le i\le r\),\([p,i]\) 区间答案,然后把两边的答案合并起来双指针就行了。这个明显是 \(O(n)\) 的。
为什么要说这个呢?因为这道题是要在线维护一个很奇怪的双指针。baka trick 可以看作有两个栈可以撤销操作,但是这题只给你一个栈去做。现在要考虑更加巧妙的重构。
你现在要把在所有重构的边左边的和重构边右边的全部丢进一个栈里。左边的称为黑边,右边的称为白边。我们撤销的时候要弹掉黑点,但是压进来的是白边。所以要想办法在每次弹的时候把黑边往栈顶移。
赛时有种做法:弹栈直到弹出来的黑边和白边数量相等。然后把白边和黑边放回去。好像说可以证明复杂度时根号级别的,常数较小,所以能过。但是看不懂证明啊。
然后写 std 做法。考虑一个二进制分组,设现在栈中有 \(s\) 个黑边,那么考虑把他按照二进制拆开。比如现在黑边最靠栈顶的连续段有连续 \(x\) 个黑边, \(x=2^p_1+2^p_2+...p_k(p1>p2>...p_k)\),那么可以把 \(2^p_k\) 给全部丢到前面去,也就是弹出 \(\mathrm{lowbit}(s)\) 个黑边(容易证明是连续的),放到白边前面,然后再弹出。发现每个黑边最多走 \(log_n\) 次就到最前面,每个白边只会被跨越 \(log n\) 次。所以总次数是 \(2nlogn\) 级别的。
#include "subway.h"
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int st[N],n,k,l,tp,r,p[N],s;
void init(int x,int m,int lim)
{
n=m;
}
void rebuild()
{
for(int i=l;i<=r;i++)
undo();
tp=0;
for(int i=r;i>=l;i--)
st[++tp]=i,merge(i);
k=r;
}
int solve(int l)
{
::l=l;
if(l^1)
{
if(st[tp]==l-1)
{
--tp;
undo();
}
else
{
int m=0;
while(st[tp]>k)
p[++m]=st[tp--],undo();
undo(),--tp;
int c=0;
for(int c=1;c<((k-l+2)&(l-k-2));c++)
p[++m]=st[tp--],undo();
for(int i=m;i;i--)
if(p[i]>k)
st[++tp]=p[i],merge(p[i]);
for(int i=m;i;i--)
if(p[i]<=k)
st[++tp]=p[i],merge(p[i]),++s;
}
}
while(r<n&&check(r+1))
merge(++r),st[++tp]=r;
if(l>k)
rebuild();
// printf("%d %d %d\n",l,r,k);
// for(int i=1;i<=tp;i++)
// printf("%d ",st[i]);
// puts("");
return r;
}