题解「Luogu5251 [LnOI2019]第二代图灵机」
转载注明来源:https://www.cnblogs.com/syc233/p/13673494.html
珂朵莉树+尺取法+线段树。
大体思路是珂朵莉树维护颜色段,线段树维护区间和、区间最值,3、4操作在珂朵莉树上做尺取法。
主要说一下尺取法的细节:
操作3
询问区间 \([l,r]\) 中包含所有(一共 \(c\) 种)颜色,数字和最小的子区间的数字和。
先将询问区间在珂朵莉树上取出来,固定区间左端点,移动右端点,用桶维护区间内每种颜色的出现次数和区间颜色总数。
当区间包含所有颜色时停止移动右端点,即算出包含所有颜色的最短区间。对于左右端点的位置进行分类讨论:
- 若左右端点同属一个块,即 \(c=1\) ,因为要求最小数字和,所以线段树取块内最小值即可。
- 否则,令左端点在块 \([l1,r1]\) 中,右端点在 \([l2,r2]\) 中,则取区间 \([r1,l2]\) 。
inline int query1(int l,int r)
{
IT itr=split(r+1),itl=split(l);
memset(sta,0,sizeof(sta));cnt=0;
int ans=INF;
for(IT l=itl,r=itl;l!=itr;++l)
{
while(r!=itr&&cnt!=c)
Add(r->val,r->r-r->l+1),++r;
--r;
if(cnt==c)
{
if(l==r) ans=min(ans,st.query_min(1,l->l,l->r));
else ans=min(ans,st.query_sum(1,l->r,r->l));
}
++r;
Del(l->val,l->r-l->l+1);
}
return ans==INF?-1:ans;
}
操作4
表示询问区间 \([l,r]\) 中没有重复颜色,数字和最大的子区间的数字和。
首先只取一个数显然是可行的,那么 \(ans\) 的初值即为区间最大值。
然后类似操作3,将区间从珂朵莉树上取出来,做尺取法。
因为只取一个数的情况已经处理,所以尺取时左右端点不能在同一个块中。要保证区间中没有重复颜色,那么每次扩展右端点时只能加入大小等于 \(1\) 的块 。然而合法区间的右端点是可能在一个大小大于 \(1\) 的块中的,因为可以只取这个块的第一个数,在每次扩展完后临时加入这种块即可。
inline int query2(int l,int r)
{
IT itr=split(r+1),itl=split(l);
memset(sta,0,sizeof(sta));cnt=0;
int ans=st.query_max(1,l,r);
for(IT l=itl,r=itl;l!=itr;++l)
{
if(l==r)
Add(r->val,1),++r;
while(r!=itr&&!sta[r->val]&&r->r-r->l+1==1)
Add(r->val,1),++r;
bool flag=false;
if(r!=itr&&!sta[r->val])
Add(r->val,1),++r,flag=true;
--r;
if(l!=r) ans=max(ans,st.query_sum(1,l->r,r->l));
Del(l->val,1);
if(flag)
Del(r->val,1),--r;// 将临时加入的块删除
++r;
}
return ans;
}
完整代码就没必要放上来了吧,相信来做这道题的人都会珂朵莉树和线段树。