codeforces 997E(线段树)

分析:

  首先考虑如何计算整个数组有多少个good区间

  容易发现一个区间是good区间当且仅当max-min-len=-1,且任意区间max-min-len>=-1

  我们可以枚举右端点,然后维护前面每个位置到当前右端点的max-min-len值,容易发现我们只需要维护区间最小值和最小值的个数就行了,于是用线段树即可

  于是我们可以得到以某个点为右端点的时候合法区间总数,那么我们把每次的结果加起来,就得到了整个数组有多少个good区间

  每次右端点移动怎么更新呢?显然我们只需要用一个max单调栈,一个min单调栈来帮助寻找修改区间,容易知道修改区间的个数是O(n)级别的,所以整个修改是O(nlogn)的

  好,现在我们回到原来的问题,如何求[l,r]内有多少个good区间

  在这个问题中,我们实际上要额外知道“每个区间的历史答案和”,这是什么意思呢,比如说在i=7的时候,[4,7]里的答案存的是有多少个合法的位置是和7匹配的

  但i=6的时候,[4,7]里的答案存的是有多少个合法的位置是和6匹配的

  我们自然而然就考虑到把每个右端点时刻,一个区间的产生的答案值加起来就行了,但这个时间复杂度是巨大的

  这里我们仍然可以用延迟标记来解决,比如右端点i枚举完了,我们可以给[1,i]上个tag,表示这整个区间的历史答案需要加上现在时刻的答案

  时间复杂度O(nlogn)

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define mp make_pair
  4 const int maxn=120000;
  5 struct wjmzbmr
  6 {
  7     int mi,num,tag;
  8     int time;
  9     long long ans;
 10 }tree[maxn*4+5];
 11 vector<pair<int,int> > q[maxn+5];
 12 long long ans[maxn+5];
 13 int a[maxn+5],s[maxn+5],s1[maxn+5];
 14 int len,len1;
 15 int n,m;
 16 void addtag(int k,int x)
 17 {
 18     tree[k].tag+=x;
 19     tree[k].mi+=x;
 20 }
 21 void addtime(int k,int x)
 22 {
 23     tree[k].time+=x;
 24     tree[k].ans+=1LL*x*tree[k].num;
 25 }
 26 void pushdown(int k)
 27 {
 28     if(tree[k].tag)
 29     {
 30         addtag(k*2,tree[k].tag);
 31         addtag(k*2+1,tree[k].tag);
 32         tree[k].tag=0;
 33     }
 34     if(tree[k].time)
 35     {
 36         if(tree[k].mi==tree[k*2].mi) addtime(k*2,tree[k].time);
 37         if(tree[k].mi==tree[k*2+1].mi) addtime(k*2+1,tree[k].time);
 38         tree[k].time=0;
 39     }
 40 }
 41 void update(int k)
 42 {
 43     tree[k].mi=min(tree[k*2].mi,tree[k*2+1].mi);
 44     tree[k].ans=tree[k*2].ans+tree[k*2+1].ans;
 45     tree[k].num=0;
 46     if(tree[k].mi==tree[k*2].mi) tree[k].num+=tree[k*2].num;
 47     if(tree[k].mi==tree[k*2+1].mi) tree[k].num+=tree[k*2+1].num;
 48 }
 49 void build(int k,int l,int r)
 50 {
 51     if(l>r) return;
 52     if(l==r)
 53     {
 54         tree[k].num=1;
 55         tree[k].mi=-1;
 56         return;
 57     }
 58     int mid=(l+r)>>1;
 59     build(k*2,l,mid);
 60     build(k*2+1,mid+1,r);
 61     update(k);
 62 }
 63 void modify(int k,int l,int r,int ql,int qr,int x)
 64 {
 65     if(r<ql||l>qr||l>r) return;
 66     if(l>=ql&&r<=qr)
 67     {
 68         addtag(k,x);
 69         return;
 70     }
 71     if(l==r) return;
 72     pushdown(k);
 73     int mid=(l+r)>>1;
 74     modify(k*2,l,mid,ql,qr,x);
 75     modify(k*2+1,mid+1,r,ql,qr,x);
 76     update(k);
 77 }
 78 void modify1(int k,int l,int r,int ql,int qr,int x)
 79 {
 80     if(r<ql||l>qr||l>r) return;
 81     if(l>=ql&&r<=qr)
 82     {
 83         if(tree[k].mi==-1)
 84         addtime(k,x);
 85         return;
 86     }
 87     if(l==r) return;
 88     pushdown(k);
 89     int mid=(l+r)>>1;
 90     modify1(k*2,l,mid,ql,qr,x);
 91     modify1(k*2+1,mid+1,r,ql,qr,x);
 92     update(k);
 93 }
 94 long long query(int k,int l,int r,int ql,int qr)
 95 {
 96     if(r<ql||l>qr||l>r) return 0;
 97     if(l>=ql&&r<=qr) return tree[k].ans;
 98     if(l==r) return 0;
 99     pushdown(k);
100     int mid=(l+r)>>1;
101     long long ans=query(k*2,l,mid,ql,qr)+query(k*2+1,mid+1,r,ql,qr);
102     update(k);
103     return ans;
104 }
105 int main()
106 {
107     scanf("%d",&n);
108     for(int i=1;i<=n;++i) scanf("%d",&a[i]);
109     scanf("%d",&m);
110     for(int i=1;i<=m;++i)
111     {
112         int l,r;
113         scanf("%d%d",&l,&r);
114         q[r].push_back(mp(l,i));
115     }
116     build(1,1,n);
117     for(int i=1;i<=n;++i)
118     {
119         while(len&&a[s[len]]<a[i]) modify(1,1,n,s[len-1]+1,s[len],a[i]-a[s[len]]),--len;
120         s[++len]=i;
121         //for(int j=1;j<=len;++j) printf("%d ",a[s[j]]);printf("\n");
122         while(len1&&a[s1[len1]]>a[i]) modify(1,1,n,s1[len1-1]+1,s1[len1],a[s1[len1]]-a[i]),--len1;
123         s1[++len1]=i;
124         //for(int j=1;j<=len1;++j) printf("%d ",a[s1[j]]);printf("\n");
125         modify1(1,1,n,1,i,1);
126         for(auto x:q[i]) ans[x.second]=query(1,1,n,x.first,i);
127         //printf("%lld\n",query(1,1,n,1,i));
128         //if(i==4) printf("%d\n",tree[2].ans);
129         modify(1,1,n,1,i,-1);
130
131     }
132     for(int i=1;i<=m;++i) printf("%lld\n",ans[i]);
133     return 0;
134 }

原文地址:https://www.cnblogs.com/wmrv587/p/9261128.html

时间: 2024-07-29 05:17:13

codeforces 997E(线段树)的相关文章

Codeforces 384E 线段树+dfs序

题目链接:点击打开链接 题意: 给定n个点,m个询问的无向树(1为根) 下面n个数表示每个点的权值 下面n-1行给出树 操作1:x点权值+v, x的第 i & 1 的儿子-v, 第 !(i&1) 的儿子+v 操作2:询问x点权值 dfs把树转成序列 根据深度把点分成2组 分别用线段树维护.. 然后Y一下 #include<stdio.h> #include<string.h> #include<iostream> #include<algorith

Codeforces 446C 线段树 递推Fibonacci公式

聪哥推荐的题目 区间修改和区间查询,但是此题新颖之处就在于他的区间修改不是个定值,而是从L 到 R 分别加 F1.F2....Fr-l+1 (F为斐波那契数列) 想了一下之后,觉得用fib的前缀和来解决,每次做懒惰标记记录下当前区间是从哪个L开始加起的,敲了一半之后发现有问题,就跟上次遇到的懒惰标记问题一样,这是个覆盖性的懒惰标记,每次向下传递后,都要先清除孩子的,清除孩子的也有可能要清除son's son,所以要一直pushdown下去,否则就会错,但这样就会超时. 能不能有个累加型的标记让我

Bash and a Tough Math Puzzle CodeForces 914D 线段树+gcd数论

Bash and a Tough Math Puzzle CodeForces 914D 线段树+gcd数论 题意 给你一段数,然后小明去猜某一区间内的gcd,这里不一定是准确值,如果在这个区间内改变一个数的值(注意不是真的改变),使得这个区间的gcd是小明所猜的数也算小明猜对.另一种操作就是真的修改某一点的值. 解题思路 这里我们使用线段树,维护区间内的gcd,判断的时候需要判断这个区间的左右子区间的gcd是不是小明猜的数的倍数或者就是小明猜的数,如果是,那么小明猜对了.否则就需要进入这个区间

Codeforces 833B 线段树优化 dp

Codeforces  833B  The Bakery 题意: n 个数要分成 k 块,每块的价值是其不同数的个数,问价值和最大是多少. tags: dp[i][j]表示前 j 个数分成 i 块的最大权值和,转移: dp[i][j] = max( dp[i-1][k] + val[k+1][j] ) , k是 1~j . 但这个过程其实并不好转移,要利用累加的特点,用线段树进行优化 (感觉我不看题解是想不到的,2333) 大概就是,对于第 i 层,我们假定已经知道了第 i-1 层,也就是求出了

Codeforces 938G 线段树分治 线性基 可撤销并查集

Codeforces 938G Shortest Path Queries 一张连通图,三种操作 1.给x和y之间加上边权为d的边,保证不会产生重边 2.删除x和y之间的边,保证此边之前存在 3.询问x到y的路径异或最小值 保证图在任意时刻连通 首先连通图路径异或相当于从x到y的任意一条路径再异或上若干个环得到的,只要在dfs过程中把非树边成的环丢到线性基里就好了,其他环一定可以通过这些环异或组合出来 有加边删边操作怎么做呢?线段树时间分治!注意到不能保证在线段树的任意一个节点图是连通的,需要用

codeforces 589G:线段树+二分

离线做 先按照t[]建线段树,维护区间的有效天数以及可用时间 然后把t[]从小到达排序,记得记录t中元素在线段树中的位置 把询问按照d从小到大排序,依次考虑 由于按照d排序,所以当前询问肯定是d最短的,我们在t数组中找到大于当前d的第一个元素位置,把这之前的元素全部从线段树中删除,表示这些天数无效 因为当前d已经是最小,所以删除对后续不会有影响 二分一个天数,查找0~mid天一共的工作时间(工作时间=总可用时间-准备时间*有效天数) #include"cstdio" #include&

CodeForces 343D 线段树维护dfs序

给定一棵树,初始时树为空 操作1,往某个结点注水,那么该结点的子树都注满了水 操作2,将某个结点的水放空,那么该结点的父亲的水也就放空了 操作3,询问某个点是否有水 我们将树进行dfs, 生成in[u], 访问结点u的时间戳,out[u],离开结点u的时间戳 每个结点的in值对应在线段树中的区间的一点 那么对于操作1, 只要将区间[in[u],out[u]] 的值都改为1, 但是如果区间[in[u],out[u]] 原先存在为0的点,那么父区间肯定是空的,这个操作不能 改变父区间的状态,所以需要

Codeforces 558E 线段树处理字符串内排序

给出长度为n的字符串,m个操作. 每一个操作有三个值 l,r,op. op==1,表示将字符串中[ l ,r ]的部分依照升序排列. op==0,表示将字符串中[ l ,r ]的部分依照降序排列. 输出终于的字符串 按小写字母建26颗线段树 对于每次改动,先记录[l,r]区间内各个字母出现的次数,并对对应区间清空,然后依照升序或者降序从新更新 #include "stdio.h" #include "string.h" char str[100010]; int n

Blog Post Rating CodeForces - 806E (线段树二分)

题目链接 题目大意: 有一个博客, 初始分数为$0$, 有$n$个人, 第$i$个人有一个期望值$a_i$, 如果第$i$个人浏览博客时,博客赞数高于$a_i$博客分数$-1$, 低于$+1$, 相等不变, 对于每个$i$, 求出$[1,i]$的人按任意顺序浏览博客后最大分数. 题解: 首先, 用贪心可以知道所有人按期望升序排列, 最后得分一定最大 由于期望有负数, 博客分数一定是先减后增的, 然后对这两段分类讨论 对于递减的段, 最后分数为递增递减的临界值 假设临界值为$x$, 设比$x$小的