转载注明来源: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;
}

完整代码就没必要放上来了吧,相信来做这道题的人都会珂朵莉树和线段树。