洛谷P5648
这题花了很长时间,是在线段树题单里找到的( )。有线段树做法,但是我感觉可能比倍增做法更难看懂。以后有空再看看吧。感觉线段树现在只会板子题,绿稍微难点可能就不会。
花了很久时间之后,就觉得自己有必要去发一篇博客来纪念一下,毕竟花了这么久的时间。中午看的时候,突然发现线段树我不知道怎么弄……然后只能看题解了,感觉看着都很麻烦的,我真的感觉自己很难看懂题解,有主席树和线段树做法,但是我有点不太懂,倍增也有点不懂。
然后晚上,让 chat 给我解释了一下,就看得差不多了。其实大佬写的题解很好,只是我有些变量其实没看懂干啥用的,没仔细看吧,但是幸好现在已经解决了。后来交的时候一直 RE , WA ,改了几个东西之后过了,现在也想明白原因了。
题目大意
给你一个数组 a a a , 给出 q q q 次询问 ,每次给出 l , r l,r l,r ,求下列式子的和。
∑ i = l r max l ≤ j ≤ i a j \sum_{i=l}^{r} \max_{l\le j\le i}a_j i=l∑rl≤j≤imaxaj
如果 l l l 确定,那么 a l a_l al 一定会被一直加直到下一个最大值出现或者 i > r i>r i>r , 所以相当于我们一个数字在它和它的下一个最大值出现之前,一直会被加。所以我们可以预处理出每一个 a i a_i ai的下一个比它大的数字,这样做只能拿 90 90 90 分看讨论区说。如果遇到一直递增的数据,复杂度就很高。
可以使用倍增 , n x t i , j nxt_{i,j} nxti,j 表示从第 i i i 个点跳转到的下一个点,这里跳转指的其实就是它后面的第多少个新的最大值。用 f i , j f_{i,j} fi,j 表示 i i i 跳转 2 j 2^j 2j 个点的答案。
然后就有
n x t i , j = n x t n x t i , j − 1 , j − 1 nxt_{i,j}=nxt_{nxt_{i,j-1},j-1} nxti,j=nxtnxti,j−1,j−1
f i , j = f i , j − 1 + f n x t i , j − 1 , j − 1 f_{i,j}=f_{i,j-1}+f_{nxt_{i,j-1},j-1} fi,j=fi,j−1+fnxti,j−1,j−1
第一个式子是显然的,但第二个式子我想了很久,虽然确实很简单,当时我没太理解 n x t nxt nxt 数组吧,其实就是只会跳转到比它大的数字,所以后面那部分的答案就不用再考虑前面的部分。线段树做法貌似也是这样的思路,以后看看。
//created: 2024-10-08 20:27:01
// #define SKADI
#if defined(YUANSHEN)
#include<D:/Tovi/template/my_template.hpp>
#else
#include<bits/stdc++.h>
using namespace std;
#endif
#ifndef SKADI
#define dbg(...) 42
#endif
template <typename T1, typename T2> void cmin(T1 &x, const T2 &y) {
x = x < y ? x : y;
}
template <typename T1, typename T2> void cmax(T1 &x, const T2 &y) {
x = x > y ? x : y;
}
using ll = long long;
using ull = unsigned long long;
using pii = pair<int,int>;
using pll = pair<ll,ll>;
#define fixset(x) fixed<<setprecision(x)
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
#define ALL(x) (x).begin()+1,(x).end()
const int INF = 1000000000;
const ll LNF = 1000000000000000000;const int N = 5e5+10;
ll f[N][22];
int n,a[N],nxt[N][22];void init()
{vector<int>stk;//nxt[i][0]找到的是右边第一个比a[i]大的数字的下标,用单调栈就行for(int i=1;i<=n;i++){while(!stk.empty()&&a[stk.back()]<a[i]){nxt[stk.back()][0]=i;stk.pop_back();}stk.push_back(i);}while(!stk.empty()){nxt[stk.back()][0]=n+1;stk.pop_back();}//剩下的说明没有数字比它大 所以就弄成n+1,而且能确保所有点都被初始化nxt[n+1][0]=n+1;//这个一定要,后面会用到的for(int i=1;i<=n;i++){f[i][0]=1LL*a[i]*(nxt[i][0]-i);}//很显然,这一部分的和就等于a[i]直接乘以a[i]是最大值的区间长度for(int i=1;i<=21;i++){for(int j=1;j<=n+1;j++)//边界一定要n+1nxt[j][i]=n+1;for(int j=1;j+(1<<i)<=n+1;j++){//因为这里更新的话,如果你前面这个nxt已经是最大的话,那么这里nxt[n+1][]会没有,如果不初始化nxt[j][i]=nxt[nxt[j][i-1]][i-1];f[j][i]=f[j][i-1]+f[nxt[j][i-1]][i-1];}}
}
void solve()
{int q;cin>>n>>q;for(int i=1;i<=n;i++)cin>>a[i];init();ll lst=0;//这里也开 ll ,有可能超的,而且小心异或边负数了while(q--){ll l,r;//注意开ll , 看讨论区有人说可能爆int cin>>l>>r;l=1+(l^lst)%n;r=(r^(lst+1))%(n-l+1)+l;ll cur=0,pos=l;for(int i=21;i>=0;i--){if(nxt[pos][i]>r) continue;cur+=f[pos][i];pos=nxt[pos][i];}cur+=1LL*a[pos]*(r-pos+1);//注意别爆了lst=cur;cout<<lst<<'\n';}
}int main()
{
#ifndef SKADIios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#endifint T=1;// cin>>T;while(T--)solve();return 0;
}
感觉这题对我真挺有难度的,感谢各位大佬的题解。我本来半天没找到自己哪写错了,刚开始一直 R E RE RE,然后 W A WA WA ,都是样例能过但分数 0 0 0,等等,为啥 R E RE RE来着,好像又想不明白了来着。
O K OK OK ,找到了, R E RE RE 是因为没开 l l ll ll 然后异或成负数了然后下标为负了就寄了吧,这数据挺好的哈,全部 R E RE RE 。洛谷的讨论区真是个好东西,感谢各位大佬。然后 W A WA WA 是因为初始话没有初始化 n x t n + 1 , i nxt_{n+1,i} nxtn+1,i导致很多 n x t nxt nxt 变成 0 0 0 了。这个很久很久才看出来,还是我突然才想起来得对拍一下才发现的。
然后后来我改了那个对了,然后又感觉自己改了很多东西,或者没改啥,就想对比一下差异,但是没找到啥好的插件, C F CF CF 里的 c o m p a r e compare compare 也是确实挺不错的哈。
今天还算是可以的,虽然感觉还是没写什么,但是还是写了的,明天继续吧,至少今天不用愧疚了~
写这个花了挺久,一小时吧差不多,但是值得吧。发现我 t y p o r a typora typora 其实没有打开那个支持 l a t e x latex latex 的设置,然后现在看着爽多了,然后也在设置里发现,可以自动把添加的图片的路径复制到指定目录。挺不错的。
睡觉了等下,然后看看博客效果,以后多刷绿题!!!现在 42 42 42 ,然后感觉这个 h e x o hexo hexo 博客 l a t e x latex latex 显示效果不是很好,以后考虑换个主题。
老师调课了,明天不用早八,很爽。
create: 2024-10-09 00:15:07