P6604 [HNOI2016] 序列 加强版

链接:P6604 [HNOI2016] 序列 加强版
首先,像这种题可以转化为计算贡献,即计算每一个元素成为最小值的次数。

这个次数怎么求呢?显然单调栈模板,对于每一个数计算左边和右边第一个比它小的数l[i]r[i]

1|0CODE 1:

for(int i=1;i<=n;i++){ while(k && a[i]<a[sta[k]]){ k--; } if(!k) l[i]=0; else{ l[i]=sta[k]; } sta[++k]=i; } k=0; for(int i=n;i>=1;i--){ while(k && a[i]<a[sta[k]]){ k--; } if(!k) r[i]=n+1; else{ r[i]=sta[k]; } sta[++k]=i; }

求出l[i]r[i]后,我们发现虽然在线询问[l,r],不好做,但是询问[1,r][l,n]是好做的,考虑先求出这两者,观察能否推广。

dp[i]为以i结尾的所有字串的最小值之和,显然左端点在l[i]之前的部分最小值跟l[i]+1i这一段毫无关系,所以这一部分的答案是dp[l[i]]

而对于左端点在l[i]之后的部分,显然此时最小值是a[i](第i的数的值),这一种情况有il[i]个区间,贡献是a[i]×(il[i])

综上,dp[i]=dp[l[i]]+a[i]×(il[i])

相同地,令DP[i]为从i开始的的所有字串的最小值之和,有DP[i]=DP[r[i]]+a[i]×(r[i]i)

sum[i][1,i]的答案,显然就是以1i结尾的所有字符串的最小值之和,即sum[i]=j=1idp[j]

同样的,令SUM[i][i,n]的答案,有SUM[i]=j=inDP[j]

2|0CODE 2:

for(int i=1;i<=n;i++){ dp[i]=dp[l[i]]+a[i]*(i-l[i]); } for(int i=n;i>=1;i--){ DP[i]=DP[r[i]]+a[i]*(r[i]-i); } for(int i=1;i<=n;i++){ sum[i]=sum[i-1]+dp[i]; } for(int i=n;i>=1;i--){ SUM[i]=SUM[i+1]+DP[i]; }

现在考虑怎么求出[l,r]类型的答案。考虑分类贡献。我们使用st表预处理出[l,r]区间内的最小值所在位置pos

那么[l,r]的所有子区间分为3种:

  • 1.左端点和右端点在pos两侧
  • 2.左端点和右端点在pos左侧
  • 3.左端点和右端点在pos右侧

第一种情况很简单,区间的个数是(posl+1)×(rpos+1),每一个区间的最小值都是pos,所以这部分的贡献是(posl+1)×(rpos+1)×apos

考虑第二种情况,你可能认为直接就是SUM[l]SUM[pos],其实不然。因为这样只确定了左端点在[l,pos)内而没有限制右端点。

考虑容斥,减去右端点在pos右侧的情况。此时左端点在[l,pos)内,有posl种选择,对于每一种选择,既然pos[l,r]的最小位置,右端点落在pos右侧的贡献自然是DP[pos]。所以要减去DP[pos]×(posl)

综上,第二种情况的贡献是SUM[l]SUM[pos]DP[pos]×(posl)

同样的,第三种情况的贡献是sum[r]sum[pos]dp[pos]×(rpos)

把这三者加起来就是[l,r]的贡献了。

下面是完整代码(ps:有的变量重名改了名)

3|0CODE 3:

#include<bits/stdc++.h> #define int long long using namespace std; int sum[100011]; int SUM[100011]; int dp[100011]; int DP[100011]; int st[100011][21],pos[100011][21]; int n,q,type,sta[100011],k; int a[100011]; int l[100011]; int r[100011]; unsigned long long re=0; namespace gen{ typedef unsigned long long ull; ull s,a,b,c,lastans=0; void in(){ cin>>s>>a>>b>>c; } ull rand(){ return s^=(a+b*lastans)%c; } }; signed main(){ cin>>n>>q>>type; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++){ st[i][0]=a[i]; pos[i][0]=i; } for(int j=1;(1<<j)<=n;j++){ for(int i=1;i+(1<<j)-1<=n;i++){ if(st[i][j-1]<=st[i+(1<<j-1)][j-1]){ st[i][j]=st[i][j-1]; pos[i][j]=pos[i][j-1]; } else{ st[i][j]=st[i+(1<<j-1)][j-1]; pos[i][j]=pos[i+(1<<j-1)][j-1]; } } } for(int i=1;i<=n;i++){ while(k && a[i]<a[sta[k]]){ k--; } if(!k) l[i]=0; else{ l[i]=sta[k]; } sta[++k]=i; } k=0; for(int i=n;i>=1;i--){ while(k && a[i]<a[sta[k]]){ k--; } if(!k) r[i]=n+1; else{ r[i]=sta[k]; } sta[++k]=i; } for(int i=1;i<=n;i++){ dp[i]=dp[l[i]]+a[i]*(i-l[i]); } for(int i=n;i>=1;i--){ DP[i]=DP[r[i]]+a[i]*(r[i]-i); } for(int i=1;i<=n;i++){ sum[i]=sum[i-1]+dp[i]; } for(int i=n;i>=1;i--){ SUM[i]=SUM[i+1]+DP[i]; } if(type==1){ gen::in(); } int lt,rt; for(int i=1;i<=q;i++){ if(!type){ cin>>lt>>rt; } else{ lt=gen::rand()%n+1; rt=gen::rand()%n+1; if(lt>rt) std::swap(lt,rt); } int poss; int kk=log2(rt-lt+1); if(a[pos[lt][kk]]<=a[pos[rt-(1<<kk)+1][kk]]) poss=pos[lt][kk]; else poss=pos[rt-(1<<kk)+1][kk]; unsigned long long ans=(unsigned long long)(a[poss]*(poss-lt+1)*(rt-poss+1)); ans+=(unsigned long long)(sum[rt]-sum[poss]-dp[poss]*(rt-poss)); ans+=(unsigned long long)(SUM[lt]-SUM[poss]-DP[poss]*(poss-lt)); gen::lastans=ans; re^=ans; } cout<<re; return 0; }

__EOF__

本文作者星河倒注
本文链接https://www.cnblogs.com/wangwenhan/p/17656605.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   星河倒注  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示