P1052 序列询问 官方题解
Gioush OJ · P1052 序列询问
序列询问
【题意简述】
给定一个二进制序列。若当前元素与栈顶相同,则弹出栈顶,否则压入当前元素,由此定义序列的消除结果。
对每次询问 ,枚举分割点 ,分别消除左右两段,再把两个消除结果拼接并继续消除。求所有分割点在拼接以后新消除的元素对数之和。
【数据点 1∼31\sim 31∼3】
【题目描述】
数据范围允许按照定义直接模拟。
【Hint】
对每个分割点分别用栈处理左右两段,再拼接两个栈中的序列。
【解法】
记录拼接后新弹出的元素对数,并对全部分割点求和。时间复杂度为 。
【数据点 4∼94\sim 94∼9】
【题目描述】
需要把单个分割点的计算降至常数时间。
【Hint】
消除结束后的序列中不存在相邻且相同的元素,因此它一定是一个 交替序列。
【解法】
对每次询问,从左向右维护所有前缀的消除长度,从右向左维护所有后缀的消除长度。加入一个元素时,消除结果的长度只会增加 或减少 。
预处理完成后,可以在 时间计算一个分割点,单次询问复杂度为 ,总时间复杂度为 。
【数据点 10∼1510\sim 1510∼15】
【题目描述】
需要用一个代数量描述区间消除后的长度。
【Hint】
定义
【解法】
先证明下标奇偶性。设当前被消去的两个元素来自原序列中的位置 。它们在当前序列中相邻,说明原来位于二者之间的 个元素已经全部被成对消去。因此 为偶数, 的奇偶性相反。
被消去的两个元素数值相同、下标奇偶性相反,所以对应的 互为相反数。每次消除都不会改变区间内 的总和。
再考虑最终剩余序列中相邻的两个元素。二者之间的原序列元素同样已经全部被成对消去,所以它们的原下标奇偶性相反;而最终序列不存在相邻且相同的元素,所以它们的数值也相反。数值与下标奇偶性同时改变以后,“二者是否相等”这一关系不变,因此相邻剩余元素对应的 相同。由此,全部剩余元素对应的 均同号。
消除前后 的总和不变,而消除结束后每一项都等于 或都等于 ,故剩余长度恰好等于这个总和的绝对值:
枚举分割点即可做到 。
【正解】
【题目描述】
需要对一个询问中的全部分割点同时求和。
【Hint】
对询问 ,记
把分割点 的贡献写成关于 的绝对值表达式。
【公式】
还需要说明分段消除不会改变最终结果。对任意两个序列 ,先处理 后,栈中恰好留下 ;继续把 依次送入同一个栈,与把 从头送入栈完全相同。因此
左右两段消除后的长度分别为 与 ,拼接并继续消除后的长度因而等于整个区间的消除长度 。每次新消除恰好删去两个元素,所以
定义
对 求和,得到
【离线计算】
每个原询问被拆成两个形如 的离线询问。
将位置 按照 从小到大排序,将全部离线询问按照 从小到大排序。扫描询问时,把所有满足 的位置加入数据结构。使用两棵树状数组,分别维护这些位置的数量与 之和。
记区间内 的数量与和为 ,全部元素的数量与和为 ,则
前两项统计不大于 的部分,后两项统计大于 的部分。全部元素的和 可以由 的普通前缀和求出。
【复杂度分析】
单组测试数据的时间复杂度为 ,空间复杂度为 。
【参考代码】
/*Author:EhundateghDate:2026/7/24Name:query.cppYou steal,I kill.*/#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 500010#define MAXQ 1000010using namespace std;int n,q,Line[MAXN],Pre[MAXN],Position[MAXN],Left[MAXN],Right[MAXN];long long IndexSum[MAXN],Cnt[MAXN],Sum[MAXN],Part[MAXQ],Ans[MAXN];struct ask{ int Left,Right,Value,Id;}Ask[MAXQ];bool cmpa(int x,int y){return Pre[x]<Pre[y];}bool cmpb(ask x,ask y){return x.Value<y.Value;}int lowbit(int x){return x&-x;}void Modify(int x,long long Val,long long C[]){ for(;x<=n;x+=lowbit(x)) C[x]+=Val;}long long Query(int x,long long C[]){ long long Ret=0; for(;x;x-=lowbit(x)) Ret+=C[x]; return Ret;}void Solve(){ static int LastN=0; scanf("%d%d",&n,&q); int ClearN=max(n,LastN); memset(Cnt,0,sizeof(long long)*(ClearN+1)); memset(Sum,0,sizeof(long long)*(ClearN+1)); Pre[0]=IndexSum[0]=0; LastN=n; for(int i=1;i<=n;i++){ scanf("%d",&Line[i]); Pre[i]=Pre[i-1]+(Line[i]==(i&1)?1:-1); IndexSum[i]=IndexSum[i-1]+Pre[i]; Position[i]=i; } int Total=0; for(int i=1;i<=q;i++){ scanf("%d%d",&Left[i],&Right[i]); Ask[++Total]={Left[i],Right[i]-1,Pre[Left[i]-1],i*2-1}; Ask[++Total]={Left[i],Right[i]-1,Pre[Right[i]],i*2}; } sort(Position+1,Position+n+1,cmpa); sort(Ask+1,Ask+Total+1,cmpb); int Now=1; for(int i=1;i<=Total;i++){ while(Now<=n&&Pre[Position[Now]]<=Ask[i].Value){ Modify(Position[Now],1,Cnt); Modify(Position[Now],Pre[Position[Now]],Sum); Now++; } int l=Ask[i].Left,r=Ask[i].Right; long long CntLow=Query(r,Cnt)-Query(l-1,Cnt); long long SumLow=Query(r,Sum)-Query(l-1,Sum); long long Count=r-l+1,SumAll=IndexSum[r]-IndexSum[l-1]; Part[Ask[i].Id]=1ll*Ask[i].Value*CntLow-SumLow+ SumAll-SumLow-1ll*Ask[i].Value*(Count-CntLow); } for(int i=1;i<=q;i++){ Ans[i]=(Part[i*2-1]+Part[i*2]- 1ll*(Right[i]-Left[i])*abs(Pre[Right[i]]-Pre[Left[i]-1]))/2; printf("%lld\n",Ans[i]); }}int main(){ int c,T; scanf("%d%d",&c,&T); while(T-->0) Solve(); return 0;}