题解 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\)对了此题还有多倍经验,都是一样的:
- P1997 faebdc 的烦恼 (要改输入方式)
- UVA11235 Frequent values (多组数据)
- SP1684 FREQUENT - Frequent values (多组数据)