MX 暑假集训 7.27
【官方】shino《怪物之歌》feat. 初音未来(初音ミク)
+175。
数据结构是好的。
[Ynoi2005] rmscne
题意
给定一个长度为 \(n\) 的序列 \(a\),\(m\) 次询问,每次询问给定 \(l,r\),你需要找到一个长度最小的区间 \([l',r']\in [l,r]\) 使得 \([l',r']\) 包含所有 \([l,r]\) 出现的数,输出长度最小值。
\(1\le n,m\le 2\times 10^6,1\le a_i\le n\)。
solution
显然的扫描线,考虑扫到 \(r\),对于每个 \(l\) 维护 \(p\) 满足 \([l,p]\) 包含了 \([l,r]\) 内的所有数的 \(p\) 最小值。
那么对于一个询问,合法的 \(l'\) 必然满足 \([l',r]\) 包含 \([l,r]\) 内出现过的所有数,我们找到最后一个满足条件的 \(l'\),查询 \([l,l']\) 维护的 \(p-l'+1\) 的最小值即为答案。
我们扫到第 \(i\) 个数,令 \(lst_x\) 为 \(x\) 上一次出现的位置,那么对于所有 \(l\in [lst_x+1,i]\),\(p\) 都应该修改为 \(i\),用线段树维护即可,\(p_{l'}-l+1\) 的最小值是容易维护的。
那么现在只需要找到最后一个合法的 \(l'\),我们可以用 set 维护每种颜色最后一次出现的位置,二分查找不小于 \(l\) 的最小位置,但是由于卡常过不了,考虑用并查集维护每个位置下一个位置满足这个位置的颜色最后一次出现,这样就可以小常数查询 \(l'\)。
时间复杂度 \(O(n\log n)\)。
Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e6+10;
constexpr int inf=1e9+10;
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int n,m,tot;
int a[N],ans[N],lst[N];
int head[N],nxt[N],vL[N],vid[N];
inline void add(int u,int l,int id) {tot++;vL[tot]=l;vid[tot]=id;nxt[tot]=head[u];head[u]=tot;}
int f[N];
inline int find(int x) {
if (f[x]==x) return x;
return f[x]=find(f[x]);
}
struct Segment_Tree {
int mx[N<<2],tag[N<<2];
inline int ls(int x) {return x<<1;}
inline int rs(int x) {return x<<1|1;}
inline void modify(int x,int r,int k) {tag[x]=k;mx[x]=k-r;}
inline void push_down(int x,int l,int r) {
if (!tag[x]) return;
int mid=l+r>>1,k=tag[x];tag[x]=0;
modify(ls(x),mid,k);
modify(rs(x),r,k);
}
inline void update(int x,int y,int l,int r,int now,int k) {
if (x<=l&&y>=r) return modify(now,r,k);
push_down(now,l,r);
int mid=l+r>>1;
if (x<=mid) update(x,y,l,mid,ls(now),k);
if (y>mid) update(x,y,mid+1,r,rs(now),k);
mx[now]=min(mx[ls(now)],mx[rs(now)]);
}
inline int query(int x,int y,int l,int r,int now) {
if (x<=l&&y>=r) return mx[now];
push_down(now,l,r);
int mid=l+r>>1,res=inf;
if (x<=mid) res=query(x,y,l,mid,ls(now));
if (y>mid) res=min(res,query(x,y,mid+1,r,rs(now)));
return res;
}
}tr;
int main() {
read(n);
f(i,1,n) read(a[i]),f[i]=i;
read(m);
f(i,1,m) {
int l,r;read(l);read(r);
add(r,l,i);
}
f(i,1,n) {
int x=a[i];
tr.update(lst[x]+1,i,1,n,1,i);
if (lst[x]) f[lst[x]]=lst[x]+1;
lst[x]=i;
for (int j=head[i];j;j=nxt[j]) {
int l=vL[j],id=vid[j];
ans[id]=tr.query(l,find(l),1,n,1);
}
}
f(i,1,m) printf("%d\n",ans[i]+1);
return 0;
}
【UR #19】前进四
题意
给定长度为 \(n\) 的序列,\(m\) 次操作,操作类型有单点修改和查询序列一个后缀的后缀最小值个数。
\(1\le n,m\le 10^6\)。
solution
带修改也能扫描线!
考虑用一个二维平面刻画操作和序列,横轴为序列维,纵轴为时间维,在横轴从后向前扫描线,维护每个时间的当前后缀最小值和后缀最小值更新次数,那么令当前扫到的位置上相邻两个单点修改操作时间即修改值分别为 \((x,v_1),(y,v_2)\),钦定 \(x<y\),那么在时间 \([x,y-1]\) 内,当前扫到的值就应该为 \(v_1\),将时间维像这样分成若干个由操作管辖的区间,初始值也可以看作一个时间为 \(0\) 的操作,对于每个区间进行 checkmin 操作并累加 checkmin 成功次数即可,用 Segment Tree Beats 可以做到 \(O((n+m)\log m)\)。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e6+10;
constexpr int inf=1e9+10;
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int n,m;
int ans[N];
struct Node{int v,t;};
vector<Node> vec[N];
vector<int> q[N];
struct Segment_Tree_Beats {
int mx1[N<<2],mx2[N<<2],cnt[N<<2],tg1[N<<2],tg2[N<<2],s[N<<2];
inline int ls(int x) {return x<<1;}
inline int rs(int x) {return x<<1|1;}
inline void push_up(int x) {
if (mx1[ls(x)]==mx1[rs(x)]) {
mx1[x]=mx1[ls(x)];
mx2[x]=max(mx2[ls(x)],mx2[rs(x)]);
cnt[x]=cnt[ls(x)]+cnt[rs(x)];
}
else if (mx1[ls(x)]>mx1[rs(x)]) {
mx1[x]=mx1[ls(x)];
mx2[x]=max(mx2[ls(x)],mx1[rs(x)]);
cnt[x]=cnt[ls(x)];
}
else {
mx1[x]=mx1[rs(x)];
mx2[x]=max(mx2[rs(x)],mx1[ls(x)]);
cnt[x]=cnt[rs(x)];
}
}
inline void build(int l,int r,int now) {
if (l==r) {
mx1[now]=inf;
mx2[now]=-inf;
cnt[now]=1;
return;
}
int mid=l+r>>1;
build(l,mid,ls(now));
build(mid+1,r,rs(now));
push_up(now);
}
inline void modify(int x,int k1,int k2) {
s[x]+=cnt[x]*k2;tg2[x]+=k2;
mx1[x]=k1;tg1[x]=k1;
}
inline void push_down(int x) {
int k1=tg1[x],k2=tg2[x];
if (!k1&&!k2) return;
tg1[x]=tg2[x]=0;
const int MX=max(mx1[ls(x)],mx1[rs(x)]);
if (mx1[ls(x)]==MX) modify(ls(x),k1,k2);
if (mx1[rs(x)]==MX) modify(rs(x),k1,k2);
}
inline void update(int x,int y,int l,int r,int now,int k) {
if (mx1[now]<=k) return;
if (x<=l&&y>=r&&k>mx2[now]) return modify(now,k,1);
push_down(now);
int mid=l+r>>1;
if (x<=mid) update(x,y,l,mid,ls(now),k);
if (y>mid) update(x,y,mid+1,r,rs(now),k);
push_up(now);
}
inline int query(int x,int l,int r,int now) {
if (l==r) return s[now];
push_down(now);
int mid=l+r>>1;
if (x<=mid) return query(x,l,mid,ls(now));
return query(x,mid+1,r,rs(now));
}
}tr;
int main() {
read(n);read(m);
f(i,1,n) {
int x;read(x);
vec[i].push_back({x,0});
}
tr.build(0,m,1);
f(i,1,m) {
int op,x,v;
read(op);read(x);
if (op==1) read(v),vec[x].push_back({v,i});
else q[x].push_back(i);
}
F(i,n,1) {
int lst=m;
reverse(vec[i].begin(),vec[i].end());
for (Node k:vec[i]) {
tr.update(k.t,lst,0,m,1,k.v);
lst=k.t-1;
}
for (int k:q[i]) ans[k]=tr.query(k,0,m,1);
}
f(i,1,m) if (ans[i]) printf("%d\n",ans[i]);
return 0;
}
新年的双区间操作
题意
给定一个长度为 \(n\) 的序列 \(a\) 和一个长度为 \(m\) 的操作序列,每个操作形式如下:
- 给定 \(l_1,r_1,x,l_2,r_2,y\),表示若 \(\max\limits_{i=l_1}^{r_1}\ge x\) 则对于所有 \(l_2\le i\le r_2\),\(a_i\leftarrow \max(a_i,y)\)。
令 \(f(a)\) 为初始序列为 \(a\),依次进行 \(m\) 次操作后所得到的 \(a\) 所有元素的最大值。
\(q\) 次修改,每次给定 \(p,v\) 将 \(a_p\leftarrow v\),你需要在每次修改后输出 \(f(a)\) 的值。
solution
我们发现这个操作的形式比较诡异,但是都脱离不开最大值,而且我们最终要求的也是最大值。
可以先考虑操作之间的传递,即若操作 \(i\) 执行,则操作 \(j\) 也必然执行,满足以下条件的操作 \(i\) 会使操作 \(j\) 必然执行。
- \(i<j\);
- \(y_i\ge x_j\);
- $[l_{2_i},r_{2_i}] \cap [l_{1_j},r_{1_j}] \ne \emptyset $。
发现操作间的传递构成一个 DAG,我们考虑处理出对于每个操作,若它被触发则它及它引发的操作中 \(y\) 的最大值,即如果这个操作触发,会导致序列的值至少是多少,令第 \(i\) 个操作最大的 \(y\) 为 \(f_i\)。
上面的限制条件本质上是一个三维偏序问题,我们考虑用 cdq 分治解决。
令当前分治区间为 \([l,r]\),\(mid=\frac{l+r}{2}\),先递归处理 \([mid+1,r]\) 的 \(f\) 值,再考虑将 \(mid< j\le r\) 的所有 \(f_j\) 向 \(l\le i\le mid\) 的所有 \(f_i\) 进行转移,最后递归转移 \([l,mid]\) 的 \(f\) 值即可。
那么如何处理一组跨过中点的转移呢?
对于 \([mid+1,r]\) 我们按照其触发权值(即 \(x\))排序,对于 \([l,mid]\) 按照传出权值(即 \(y\))排序,然后混合起来从小到大依次扫描,在线段树上维护每个位置权值最大值,那么加入一个 \([mid+1,r]\) 的 \(f\) 值即为在其触发区间(即 \([l_1,r_1]\))区间取 \(\max\),扫到一个 \([l,mid]\) 内的操作则在线段树对应传出区间(即 \([l_2,r_2]\))进行区间查 \(\max\),即可完成所有的转移。
考虑完操作间的传递,我们只需要求出每个 \(a_p=v\) 能触发哪些操作即可。
对于一个位置和上面的值 \((p,v)\),它能触发的操作应该满足以下条件:
- \(p\in [l_1,r_1]\);
- \(v\ge x\)。
可以将所有的点 \((p,v)\) 离线下来,按照 \(v\) 排序,再对于每个操作按照 \(x\) 排序,和上面类似,混合起来权值从小到大扫描线,加入一个操作等价于在线段树上将 \([l_1,r_1]\) 对 \(f\) 值取 \(\max\),询问一个点能够触发操作的最大 \(f\) 值相当于线段树单点查 \(\max\)。
处理完所有点后,每次修改相当于删除一个点的权值,并加入另一个点的权值,那么用 multiset 维护即可。
时间复杂度 \(O(m\log n\log m+(n+q)\log n)\)。
Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<bitset>
#include<queue>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;
inline void read(int &x) {
x=0;int f=1;
char ch=getchar();
while (ch<48) {if(ch=='-')f=-1;ch=getchar();}
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();x*=f;
}
struct Segment_Tree {
vector<int> did;
bitset<N<<2> vis;
int mx[N<<2],tg[N<<2];
inline Segment_Tree() {f(i,1,(N<<2)-1) mx[i]=tg[i]=-inf;}
inline int ls(int x) {return x<<1;}
inline int rs(int x) {return x<<1|1;}
inline void modify(int x,int k) {
mx[x]=max(mx[x],k);
tg[x]=max(tg[x],k);
if (!vis[x]) did.push_back(x),vis.set(x);
}
inline void push_down(int x) {
if (tg[x]==-inf) return;
int k=tg[x];tg[x]=-inf;
modify(ls(x),k);
modify(rs(x),k);
}
inline void update(int x,int y,int l,int r,int now,int k) {
if (x<=l&&y>=r) return modify(now,k);
push_down(now);
int mid=l+r>>1;
if (x<=mid) update(x,y,l,mid,ls(now),k);
if (y>mid) update(x,y,mid+1,r,rs(now),k);
mx[now]=max(mx[ls(now)],mx[rs(now)]);
if (!vis[now]) did.push_back(now),vis.set(now);
}
inline int query(int x,int y,int l,int r,int now) {
if (x<=l&&y>=r) return mx[now];
push_down(now);
int mid=l+r>>1,res=-inf;
if (x<=mid) res=query(x,y,l,mid,ls(now));
if (y>mid) res=max(res,query(x,y,mid+1,r,rs(now)));
return res;
}
inline void clear() {
for (int k:did) {
mx[k]=tg[k]=-inf;
vis.reset(k);
}
did.clear();
}
};
int n,m,q;
struct OPT{int l,r,v1,x,y,v2,val,id;}p[N];
struct Point{int id,x,v,w,op;};
namespace cdq_solve {
Segment_Tree tr;
inline void calc(int l,int r) {
int mid=l+r>>1;
sort(p+l,p+r+1,[&](OPT x,OPT y){
int vx=(x.id<=mid)?(x.v2):(x.v1);
int vy=(y.id<=mid)?(y.v2):(y.v1);
return (vx==vy)?(x.id>y.id):(vx<vy);
});
f(i,l,r) {
OPT now=p[i];
if (now.id<=mid) p[i].val=max(now.val,tr.query(now.x,now.y,1,n,1));
else tr.update(now.l,now.r,1,n,1,now.val);
}
sort(p+l,p+r+1,[&](OPT x,OPT y){return x.id<y.id;});
tr.clear();
}
inline void solve(int l,int r) {
if (l==r) return;
int mid=l+r>>1;
solve(mid+1,r);
calc(l,r);
solve(l,mid);
}
}
namespace Get_Point_val {
Segment_Tree tr;
inline vector<Point> work(vector<Point> &tmp) {
f(i,1,m) tmp.push_back({p[i].l,p[i].r,p[i].v1,p[i].val,0});
sort(tmp.begin(),tmp.end(),[&](Point x,Point y){return (x.v==y.v)?(x.op<y.op):(x.v<y.v);});
vector<Point> ret;
for (Point k:tmp) {
if (k.op) ret.push_back({k.id,k.x,k.v,max(k.v,tr.query(k.x,k.x,1,n,1)),k.op});
else tr.update(k.id,k.x,1,n,1,k.w);
}
sort(ret.begin(),ret.end(),[&](Point x,Point y){return x.id<y.id;});
return ret;
}
}
struct Multiset {
priority_queue<int> q,d;
inline void chk() {while(!d.empty()&&q.top()==d.top())q.pop(),d.pop();}
inline void insert(int x) {q.push(x);}
inline void erase(int x) {d.push(x);chk();}
inline int mx() {return q.top();}
}ans;
int main() {
read(n);read(m);read(q);
vector<Point> st;
f(i,1,n) {
int x;read(x);
st.push_back({0,i,x,0,1});
}
f(i,1,m) {
int l,r,v1,x,y,v2;
read(l);read(r);read(v1);read(x);read(y);read(v2);
p[i]={l,r,v1,x,y,v2,v2,i};
}
cdq_solve::solve(1,m);
f(i,1,q) {
int p,v;read(p);read(v);
st.push_back({i,p,v,0,1});
}
vector<Point> tmp=Get_Point_val::work(st);
static int val[N];
for (Point k:tmp) {
if (!k.id) {
val[k.x]=k.w;
ans.insert(k.w);
}
else {
ans.erase(val[k.x]);
ans.insert(val[k.x]=k.w);
printf("%d\n",ans.mx());
}
}
return 0;
}
Double-Rift Dial
题意
给定一个排列,你需要求出有多少个位置满足以它开始绕排列一圈得到的排列,其每个前缀中的数字在值域上构成的连续段数不超过 \(2\)。
\(1\le n\le 2\times 10^5\)。
solution
写一下昨晚 CF 的题。
考虑加入一个数会对值域上的段造成什么影响。
令加入的数为 \(x\),当前数的集合为 \(S\)。
- 若 \(x-1\not\in S\land x+1\not\in S\),说明 \(x\) 成为值域上的一个段,即段数增加 \(1\);
- 否则如果 \(x-1\in S\land x+1\in S\),说明 \(x\) 可以将相邻两个段连起来,即段数减少 \(1\);
- 否则说明 \(x-1\in S\lor x+1\in S\),说明 \(x\) 可以加入相邻数的段内,段数不变。
破环为链,考虑 \(a_i\) 会对哪些起点造成贡献。
令 \(lst_x\) 为当前 \(x\) 最后出现的位置,没出现过则为 \(0\),令 \(p_1\) 为 \(\min(lst_{a_i-1},lst_{a_i+1})\),\(p_2\) 为 \(\max(lst_{a_i-1},lst_{a_i+1})\),根据上述条件,对于一个起点 \(s\),若 \(\max(1,i-n+1) \le s\le p_1\),说明 \(a_i\) 能够合并两个段,段数减 \(1\),若 \(p_1<s\le p_2\),段数不变,若 \(p_2<s\le i\),则段数加 \(1\)。
由于一个起点的所有前缀都要满足段数不超过 \(2\),所以从左到右扫描整个序列,扫到 \(i\) 就相应地对 \([\max(1,i-n+1),p_1]\) 区间减 \(1\),对 \([p_2+1,i]\) 区间加 \(1\),并维护每个位置的历史最大值即为每个起点对应所有前缀的段数最大值。
由于只需要判断每个位置历史最大值是否超过 \(2\),所以并不需要实现懒标记最大值等标准的历史最值线段树操作,只要每次修改时对需要更新历史最值的位置暴力更新即可,每个位置更新次数最多为 \(2\),所以时间复杂度 \(O(n\log n)\)。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
const int N=4e5+10;
const int inf=2e9+10;
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int n;
int a[N],mp[N],t[N];
struct Segment_Tree {
int mx[N<<2],tg[N<<2];
inline int ls(int x) {return x<<1;}
inline int rs(int x) {return x<<1|1;}
inline void clear(int n) {f(i,1,n<<2)mx[i]=tg[i]=0;}
inline void modify(int x,int k) {mx[x]+=k;tg[x]+=k;}
inline void push_down(int x) {
if (!tg[x]) return;
int k=tg[x];tg[x]=0;
modify(ls(x),k);
modify(rs(x),k);
}
inline void update(int x,int y,int l,int r,int now,int k) {
if (x<=l&&y>=r) return modify(now,k);
push_down(now);
int mid=l+r>>1;
if (x<=mid) update(x,y,l,mid,ls(now),k);
if (y>mid) update(x,y,mid+1,r,rs(now),k);
mx[now]=max(mx[ls(now)],mx[rs(now)]);
}
inline void chk(int l,int r,int now) {
if (mx[now]<=0) return;
if (l==r) {
t[l]++;
if (t[l]>2) mx[now]=-inf;
else mx[now]=0;
return;
}
push_down(now);
int mid=l+r>>1;
chk(l,mid,ls(now));
chk(mid+1,r,rs(now));
mx[now]=max(mx[ls(now)],mx[rs(now)]);
}
}tr;
inline void update(int l,int r,int k) {
if (l>r) return;
tr.update(l,r,1,n,1,k);
tr.chk(1,n,1);
}
inline void solve() {
read(n);
f(i,1,n) read(a[i]),a[i+n]=a[i];
int rn=n;n*=2;
tr.clear(n);
int ans=0;
f(i,1,n) {
int L=max(1,i-rn+1);
int p1=min(mp[a[i]-1],mp[a[i]+1]),p2=max(mp[a[i]-1],mp[a[i]+1]);
update(L,p1,-1);
update(max(L,p2+1),i,1);
mp[a[i]]=i;
}
f(i,1,rn) ans+=(t[i]<=2);
printf("%d\n",ans);
f(i,1,n) mp[i]=t[i]=0;
}
int main() {
int T;
read(T);
while (T--) solve();
return 0;
}
【模板】李超线段树 / [HEOI2013] Segment
题意
有 \(n\) 次操作,每次操作在平面上加入一条线段或询问一个横坐标上纵坐标最大的线段编号,强制在线。
\(1\le n\le 10^5,1\le x\le 39989,1\le y\le 10^9\)。
solution
哦哦原来李超树这么简单。
维护一棵值域线段树,每个节点存一条能够覆盖整个值域区间的线段,类似于标记永久化,我们需要保证一个位置上纵坐标最大的线段一定在祖先存的线段中。
考虑加入一条线段会造成什么影响,先将这条线段拆成 \(O(\log V)\) 个区间,考虑每个区间怎么做加入直线(相当于在一个完全覆盖的值域节点加入线段,后文称为全局覆盖)。
对于一个节点上维护的线段 \(L\),我们尝试加入一条新的线段 \(k\),可以分成以下几种情况讨论:
- \(k\) 在这个节点的值域上完全覆盖 \(L\),则 \(L\) 已经完全没有意义,所以直接扔掉 \(L\),存入 \(k\);
- \(L\) 在这个节点的值域上完全覆盖 \(k\),则 \(k\) 完全没有意义,直接返回;
- \(k\) 部分在 \(L\) 之上,将在中点上更高的线段设为当前区间存的线段,令一条线段递归可能更优的那一半子区间,显然这样的子区间只有一个。
每个全局覆盖操作需要单侧递归,所以全局覆盖时间复杂度为 \(O(\log V)\),每个区间覆盖可以拆成 \(O(\log V)\) 个全局覆盖,所以总复杂度 \(O(n\log^2 V)\)。
有个李超树的小性质:动态开点李超树插入 \(m\) 条直线的空间复杂度为 \(O(m)\),而非 \(O(m\log V)\),可以根据上面单侧递归的条件证明。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e5+10;
constexpr int inf=1e9+10;
constexpr int VL=1,VR=39989;
inline void add(int &x,int y,int mod) {x+=y;x%=mod;x++;}
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
struct Line {
int id;long double k,b;
inline long double fx(int x) {return k*x+b;}
};
inline bool cmp(Line x,Line y,int p) {
long double v1=x.fx(p),v2=y.fx(p);
if (v1!=v2) return v1>v2;
return x.id<y.id;
}
struct Segment_Tree {
Line tr[N<<2];
inline int ls(int x) {return x<<1;}
inline int rs(int x) {return x<<1|1;}
inline void modify(int l,int r,int now,Line k) {
int mid=l+r>>1;
if (!tr[now].id) return tr[now]=k,void();
if (cmp(k,tr[now],mid)) swap(tr[now],k);
if (cmp(k,tr[now],l)) modify(l,mid,ls(now),k);
if (cmp(k,tr[now],r)) modify(mid+1,r,rs(now),k);
}
inline void update(int x,int y,int l,int r,int now,Line k) {
if (x<=l&&y>=r) return modify(l,r,now,k);
int mid=l+r>>1;
if (x<=mid) update(x,y,l,mid,ls(now),k);
if (y>mid) update(x,y,mid+1,r,rs(now),k);
}
inline void query(int x,int l,int r,int now,int &ret,long double ans) {
if (tr[now].id) {
long double res=tr[now].fx(x);
if (res>ans||(res==ans&&tr[now].id<ret)) ans=res,ret=tr[now].id;
}
if (l==r) return;
int mid=l+r>>1;
if (x<=mid) query(x,l,mid,ls(now),ret,ans);
else query(x,mid+1,r,rs(now),ret,ans);
}
}tr;
inline Line get_Line(int x,int y,int xx,int yy,int id) {
long double k=(long double)(y-yy)/(x-xx);
long double b=y-k*x;
return {id,k,b};
}
int main() {
int m;read(m);
int last_ans=0,cnt=0;
static int mx[N],mxy[N];
f(i,1,m) {
int op,x,y,xx,yy;
read(op);read(x);
add(x,last_ans-1,VR);
if (!op) {
last_ans=mx[x];
tr.query(x,VL,VR,1,last_ans,mxy[x]);
printf("%d\n",last_ans);
}
else {
read(y);read(xx);read(yy);
add(y,last_ans-1,1e9);
add(yy,last_ans-1,1e9);
add(xx,last_ans-1,VR);
if (x==xx) {
cnt++;y=max(y,yy);
if (mxy[x]<y) mxy[x]=y,mx[x]=cnt;
continue;
}
tr.update(min(x,xx),max(x,xx),VL,VR,1,get_Line(x,y,xx,yy,++cnt));
}
}
return 0;
}

浙公网安备 33010602011771号