题解 UVA11235 【Frequent values】

\[\texttt{前言} \]

\(\quad\)题解中怎么只有莫队和 \(st\) 表的解法(还有一个分块写法)?那么我就来分享一波萌新都可以看懂的线段树题解(我就是萌新),并不算很复杂,但胜在思路简单,主要是好想。

\[\texttt{题目大意} \]

\(\quad\)给定一个长为 \(n\) 的上升序列 \(a_i\),并给定 \(q\) 次询问,每次询问给出 \(l,r\) 询问区间 \([l,r]\) 的众数的出现次数。

\[\texttt{线段树解法} \]

\(\quad\)因为给定的是有序的序列,每种数都只出现在一段连续的区间内,所以我就想到了线段树求解,以前做过一道类似的题:CF515E Drazil and Park (我的题解)

\(\quad\)我选择用结构体来保存线段树的信息,\(Max\) 表示这个区间中出现次数最多的数的个数,\(ll\) 表示从最左边开始连续的数的数量,\(rr\) 表示从最右边开始的数的数量,\(l\) 表示区间最左端的数的值,\(r\) 表示区间最右端的数的值。

\(\quad\)其中 \(ll\)\(rr\) 在两个子区间合并的时候可以发挥作用,因为这样可以连接左右两个子区间,这个在很多区间问题上都可以巧妙运用。

\(\quad\)其他解释都在注释里,个人认为很好理解,下面是核心代码,建树和在线询问操作。

struct node{
	int Max,ll,rr,l,r;
}t[N<<2];

il void build(int k,int l,int r)//建树
{
	if(l==r){t[k].Max=t[k].ll=t[k].rr=1;t[k].l=t[k].r=a[l]=read();return;}//初始化
	int mid=l+r>>1;
	build(k<<1,l,mid);build(k<<1|1,mid+1,r);
	if(a[l]==a[mid+1])t[k].ll=mid-l+1+t[k<<1|1].ll;//左子区间都是一样的值的情况就合并
   else t[k].ll=t[k<<1].ll;
	if(a[mid]==a[r])t[k].rr=r-mid+t[k<<1].rr;//右子区间都是一样的值的情况就合并
   else t[k].rr=t[k<<1|1].rr;
	t[k].Max=max(t[k<<1].Max,t[k<<1|1].Max);//更新
	t[k].l=t[k<<1].l;t[k].r=t[k<<1|1].r;//更新
	if(a[mid]==a[mid+1])t[k].Max=max(t[k].Max,t[k<<1].rr+t[k<<1|1].ll);//左右子区间相邻的值相同时可以合并
}

il node query(int k,int l,int r,int x,int y)//返回的是结构体node
{
	if(l>=x&&r<=y)return t[k];
	int mid=l+r>>1;
	if(y<=mid)return query(k<<1,l,mid,x,y);//答案只在左区间
	if(x>mid)return query(k<<1|1,mid+1,r,x,y);//答案只在右区间
	node tmp1=query(k<<1,l,mid,x,y),tmp2=query(k<<1|1,mid+1,r,x,y),ans;//答案横跨左右区间要格外注意
	ans.Max=max(tmp1.Max,tmp2.Max);//更新过程和上面的建树一样
	ans.ll=tmp1.ll;ans.rr=tmp2.rr;
	ans.l=tmp1.l;ans.r=tmp2.r;
	if(tmp1.l==tmp2.l)ans.ll=tmp1.ll+tmp2.ll;//左子区间都是一样的值的情况就合并
	if(tmp1.r==tmp2.r)ans.rr=tmp1.rr+tmp2.rr;//右子区间都是一样的值的情况就合并
	if(a[mid]==a[mid+1])ans.Max=max(ans.Max,tmp1.rr+tmp2.ll);//左右子区间相邻的值相同时可以合并
	return ans;
}

\(\quad\)完整代码在剪贴板里。

\[\texttt{后话} \]

\(\quad\)对了此题还有多倍经验,都是一样的:

原文地址:https://www.cnblogs.com/FarkasW/p/15463153.html