线段树
题单简介
如何快速求出一个序列的区间和呢?可以使用前缀和。如何快速求出一个序列的最值呢?可
以使用 ST
表。这两种数据结构在建立的时候颇费功夫,但使用的时候效率很高。如果再增加一
个需求:时不时的修改序列的值,那么这两种数据结构就无法高效完成了。不过可以使用线段树
来解决这类问题。
在基础篇中,我们已经学习了二叉树的有关概念。而线段树是一种特殊的二叉树,它可以将
一个线性的序列组织成一个树状的结构,从而可以在对数的时间复杂度下访问序列上的任意一个
区间并进行维护。在本章中,将学习线段树的构建方法和一些简单的应用。
【模板】线段树 1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
将某区间每一个数加上 \(k\) 。
求出某区间每一个数的和。
输入格式
第一行包含两个整数 \(n,
m\) ,分别表示该数列数字的个数和操作的总个数。
第二行包含 \(n\)
个用空格分隔的整数,其中第 \(i\)
个数字表示数列第 \(i\) 项的初始值。
接下来 \(m\) 行每行包含 \(3\) 或 \(4\) 个整数,表示一个操作,具体如下:
1 x y k
:将区间 \([x,
y]\) 内每个数加上 \(k\) 。
2 x y
:输出区间 \([x,
y]\) 内每个数的和。
输出格式
输出包含若干行整数,即为所有操作 2 的结果。
样例 #1
样例输入 #1
1 2 3 4 5 6 7 5 5 1 5 4 2 3 2 2 4 1 2 3 2 2 3 4 1 1 5 1 2 1 4
样例输出 #1
提示
对于 \(30\%\) 的数据:\(n \le 8\) ,\(m
\le 10\) 。
对于 \(70\%\) 的数据:\(n \le {10}^3\) ,\(m \le {10}^4\) 。
对于 \(100\%\) 的数据:\(1 \le n, m \le {10}^5\) 。
保证任意时刻数列中所有元素的绝对值之和 \(\le {10}^{18}\) 。
【样例解释】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 #include <bits/stdc++.h> using namespace std;using ll=long long ;const ll N=1e6 ;#define rep(i,a,n) for(int i=a;i<=n;i++) ll a[N]; ll w[N*4 ]; void pushup (const int u) { w[u]=w[u*2 ]+w[u*2 +1 ]; } void build (const int u,int l,int r) { if (l==r) { w[u]=a[l]; return ; } int mid=(l+r)/2 ; build (u*2 ,l,mid); build (u*2 +1 ,mid+1 ,r); pushup (u); } ll query1 (int u,int l,int r,int p) {if (l==r){ return w[u]; } else { int mid=(l+r)/2 ; if (mid>=p) { return query1 (u*2 ,l,mid,p); } else { return query1 (u*2 +1 ,mid+1 ,r,p); } } } ll update1 (int u,int l,int r,int p,ll x) { if (l==r) { w[u]=x; } else { int mid=(l+r)>>1 ; if (mid>=p) { update1 (u*2 ,l,mid,p,x); } else { update1 (u*2 +1 ,mid+1 ,r,p,x); } pushup (u); } } bool inrange (int L,int R,int l,int r) { return (l<=L)&&(R<=r); } bool outrange (int L,int R,int l,int r) { return (L>r)||(R<l); } ll lzy[N*4 ]; void maketag (int u,int len,ll x) { lzy[u]+=x; w[u]+=len*x; } void pushdown (int u,int L,int R) { int Mid=(L+R)/2 ; maketag (u*2 ,Mid-L+1 ,lzy[u]); maketag (u*2 +1 ,R-Mid,lzy[u]); lzy[u]=0 ; } ll query (int u,int L,int R,int l,int r) { if (inrange (L,R,l,r)) { return w[u]; } else if (!outrange (L,R,l,r)) { int Mid=(L+R)/2 ; pushdown (u,L,R); return query (u*2 ,L,Mid,l,r)+query (u*2 +1 ,Mid+1 ,R,l,r); } else { return 0 ; } } void update (int u,int L,int R,int l,int r,ll x) { if (inrange (L,R,l,r)) { maketag (u,R-L+1 ,x); } else if (!outrange (L,R,l,r)) { int Mid=(L+R)/2 ; pushdown (u,L,R); update (u*2 ,L,Mid,l,r,x); update (u*2 +1 ,Mid+1 ,R,l,r,x); pushup (u); } } int main () {int n,m;cin>>n>>m; rep (i,1 ,n){ cin>>a[i]; } build (1 ,1 ,n);rep (i,1 ,m){ int op,x,y; cin>>op; ll k; if (op==1 ) { cin>>x>>y>>k; update (1 ,1 ,n,x,y,k); } else { cin>>x>>y; cout<<(query (1 ,1 ,n,x,y))<<'\n' ; } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include <bits/stdc++.h> using namespace std;const int N=1e5 +10 ;struct node { int sum; int l,r; int add; }tr[N*4 ]; int a[N];inline void pushup (int x) { tr[x].sum=tr[2 *x].sum+tr[2 *x+1 ].sum; } inline void pushudown (int x) { if (tr[x].add){ tr[2 *x].add+=tr[x].add,tr[2 *x+1 ].add+=tr[x].add; tr[2 *x].sum+=tr[x].add*(tr[2 *x].r-tr[2 *x].l+1 ); tr[2 *x+1 ].sum+=tr[x].add*(tr[2 *x+1 ].r-tr[2 *x+1 ].l+1 ); tr[x].add=0 ; } } void build (int x,int l,int r) { tr[x].l=l,tr[x].r=r,tr[x].add=0 ; if (l==r){ tr[x].sum=a[l]; return ; } int mid=(l+r)/2 ; build (2 *x,l,mid),build (2 *x+1 ,mid+1 ,r); pushup (x); } int query (int x,int l,int r) { if (l<=tr[x].l&&r>=tr[x].r) return tr[x].sum; pushudown (x); int mid=(tr[x].l+tr[x].r)/2 ,sum=0 ; if (l<=mid) sum+=query (x*2 ,l,r); if (r>mid) sum+=query (x*2 +1 ,l,r); return sum; } void update (int now,int l,int r,int k) { if (l<=tr[now].l&&r>=tr[now].r){ tr[now].sum+=k*(tr[now].r-tr[now].l+1 ); tr[now].add+=k; } else { pushudown (now); int mid=(tr[now].l+tr[now].r)/2 ; if (l<=mid) update (now*2 ,l,r,k); if (r>mid) update (now*2 +1 ,l,r,k); pushup (now); } } int n,q;int main () { cin>>n>>q; for (int i=1 ;i<=n;i++) cin>>a[i]; build (1 ,1 ,n); while (q--){ int l,r,k,c; cin>>c>>l>>r; if (c==1 ){ cin>>k; update (1 ,l,r,k); } else cout<<query (1 ,l,r)<<endl; } return 0 ; }
[TJOI2009] 开关
题目描述
现有 \(n\)
盏灯排成一排,从左到右依次编号为:\(1\) ,\(2\) ,……,\(n\) 。然后依次执行 \(m\) 项操作。
操作分为两种:
指定一个区间 \([a,b]\) ,然后改变编号在这个区间内的灯的状态(把开着的灯关上,关着的灯打开);
指定一个区间 \([a,b]\) ,要求你输出这个区间内有多少盏灯是打开的。
灯在初始时都是关着的。
输入格式
第一行有两个整数 \(n\) 和 \(m\) ,分别表示灯的数目和操作的数目。
接下来有 \(m\)
行,每行有三个整数,依次为:\(c\) 、\(a\) 、\(b\) 。其中 \(c\) 表示操作的种类。
当 \(c\) 的值为 \(0\) 时,表示是第一种操作。
当 \(c\) 的值为 \(1\) 时,表示是第二种操作。
\(a\) 和 \(b\) 则分别表示了操作区间的左右边界。
输出格式
每当遇到第二种操作时,输出一行,包含一个整数,表示此时在查询的区间中打开的灯的数目。
样例 #1
样例输入 #1
1 2 3 4 5 6 4 5 0 1 2 0 2 4 1 2 3 0 2 4 1 1 4
样例输出 #1
提示
数据规模与约定
对于全部的测试点,保证 \(2\le n\le
10^5\) ,\(1\le m\le
10^5\) ,\(1\le a,b\le n\) ,\(c\in\{0,1\}\) 。
本题要点是懒标记,由于状态只有0,1,懒标记可以采取异或的方式
每一次下放,当前节点减去所有长度即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 #include <bits/stdc++.h> using namespace std;using ll=long long ;const ll N=1e6 ;#define rep(i,a,n) for(int i=a;i<=n;i++) ll a[N]; ll w[N*4 ]; void pushup (const int u) { w[u]=w[u*2 ]+w[u*2 +1 ]; } void build (const int u,int l,int r) { if (l==r) { w[u]=a[l]; return ; } int mid=(l+r)/2 ; build (u*2 ,l,mid); build (u*2 +1 ,mid+1 ,r); pushup (u); } bool inrange (int L,int R,int l,int r) { return (l<=L)&&(R<=r); } bool outrange (int L,int R,int l,int r) { return (L>r)||(R<l); } ll lzy[N*4 ]; void maketag (int u,int len,ll x) { w[u] = len - w[u]; lzy[u] ^=1 ; } void pushdown (int u,int L,int R) { int Mid=(L+R)/2 ; maketag (u*2 ,Mid-L+1 ,lzy[u]); maketag (u*2 +1 ,R-Mid,lzy[u]); lzy[u]=0 ; } ll query (int u,int L,int R,int l,int r) { if (inrange (L,R,l,r)) { return w[u]; } else if (!outrange (L,R,l,r)) { int Mid=(L+R)/2 ; if (lzy[u]==1 ) { pushdown (u, L, R); } return query (u*2 ,L,Mid,l,r)+query (u*2 +1 ,Mid+1 ,R,l,r); } else { return 0 ; } } void update (int u,int L,int R,int l,int r,ll x) { if (inrange (L,R,l,r)) { maketag (u,R-L+1 ,x); } else if (!outrange (L,R,l,r)) { int Mid=(L+R)/2 ; if (lzy[u]==1 ) { pushdown (u,L,R); } update (u*2 ,L,Mid,l,r,x); update (u*2 +1 ,Mid+1 ,R,l,r,x); pushup (u); } } int main () {int n,m;cin>>n>>m; rep (i,1 ,n){ a[i]=0 ; } build (1 ,1 ,n);rep (i,1 ,m){ int op,x,y; cin>>op; ll k; if (op==0 ) { cin>>x>>y;; update (1 ,1 ,n,x,y,1 ); } else { cin>>x>>y; cout<<query (1 ,1 ,n,x,y)<<'\n' ; } } }
无聊的数列
题目背景
无聊的 YYB 总喜欢搞出一些正常人无法搞出的东西。有一天,无聊的 YYB
想出了一道无聊的题:无聊的数列。。。(K峰:这题不是傻X题吗)
题目描述
维护一个数列 \(a_i\) ,支持两种操作:
1 l r K D
:给出一个长度等于 \(r-l+1\) 的等差数列,首项为 \(K\) ,公差为 \(D\) ,并将它对应加到 \([l,r]\) 范围中的每一个数上。即:令 \(a_l=a_l+K,a_{l+1}=a_{l+1}+K+D\ldots
a_r=a_r+K+(r-l) \times D\) 。
2 p
:询问序列的第 \(p\) 个数的值 \(a_p\) 。
输入格式
第一行两个整数数 \(n,m\)
表示数列长度和操作个数。
第二行 \(n\) 个整数,第 \(i\) 个数表示 \(a_i\) 。
接下来的 \(m\)
行,每行先输入一个整数 \(opt\) 。
若 \(opt=1\) 则再输入四个整数 \(l\ r\ K\ D\) ;
若 \(opt=2\) 则再输入一个整数 \(p\) 。
输出格式
对于每个询问,一行一个整数表示答案。
样例 #1
样例输入 #1
1 2 3 4 5 2 1 2 3 4 5 1 2 4 1 2 2 3
样例输出 #1
提示
数据规模与约定
对于 \(100\%\) 数据,\(0\le n,m \le 10^5,-200\le a_i,K,D\le 200, 1 \leq l
\leq r \leq n, 1 \leq p \leq n\) 。
本题需要两个懒标记
一个记录首项
一个记录公差
期间有一些需要注意的事项
那些参数的传递有点细节,需要注意,调了半个月()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 #include <bits/stdc++.h> using namespace std;using ll = long long ;const ll N = 1e6 ;#define rep(i, a, n) for (ll i = a; i <= n; i++) ll a[N]; ll w[N * 4 ]; void pushup (const ll u) { w[u] = w[u * 2 ] + w[u * 2 + 1 ]; } void build (const int u, int l, int r) { if (l == r) { w[u] = a[l]; return ; } int mid = (l + r) >> 1 ; build (u * 2 , l, mid); build (u * 2 + 1 , mid + 1 , r); pushup (u); } ll first[N * 4 ]; ll cha[N * 4 ]; bool inrange (ll L, ll R, ll l, ll r) { return (l <= L) && (R <= r); } bool outrange (ll L, ll R, ll l, ll r) { return (L > r) || (R < l); } void maketag (ll u, ll len, ll x, ll y) { ll mid = x + y * (len - 1ll ); first[u] += x; cha[u] += y; w[u] += 1ll * (mid + x) * len / 2 ; } void pushdown (ll u, ll L, ll R) { ll Mid = (L + R) / 2 ; maketag (u * 2 , Mid - L + 1 , first[u], cha[u]); maketag (u * 2 + 1 , R - Mid, first[u] + (Mid - L + 1 ) * cha[u], cha[u]); cha[u] = 0 ; first[u] = 0 ; } ll query (ll u, ll L, ll R, ll l, ll r) { if (inrange (L, R, l, r)) { return w[u]; } else if (!outrange (L, R, l, r)) { ll Mid = (L + R) >> 1 ; pushdown (u, L, R); return query (u * 2 , L, Mid, l, r) + query (u * 2 + 1 , Mid + 1 , R, l, r); } else { return 0 ; } } void update (ll u, ll L, ll R, ll l, ll r, ll x, ll y) { if (inrange (L, R, l, r)) { maketag (u, R - L + 1 , x, y); } else if (!outrange (L, R, l, r)) { ll Mid = (L + R) / 2 ; pushdown (u, L, R); update (u * 2 , L, Mid, l, r, x, y); update (u * 2 + 1 , Mid + 1 , R, l, r, x + (Mid - L +1 ) * y, y); pushup (u); } } int main () { ll n, m; cin >> n >> m; rep (i, 1 , n) { cin >> a[i]; } build (1 , 1 , n); rep (i, 1 , m) { ll op, x, y; cin >> op; ll k; if (op == 1 ) { ll x, y; ll z, k; cin >> x >> y >> z >> k; update (1 , 1 , n, x, y, (1 -x)*k+z, k); } else { ll x; cin >> x; cout << query (1 , 1 , n, x, x) << '\n' ; } } }
扶苏的问题
题目描述
给定一个长度为 \(n\) 的序列 \(a\) ,要求支持如下三个操作:
给定区间 \([l,
r]\) ,将区间内每个数都修改为 \(x\) 。
给定区间 \([l,
r]\) ,将区间内每个数都加上 \(x\) 。
给定区间 \([l,
r]\) ,求区间内的最大值。
输入格式
第一行是两个整数,依次表示序列的长度 \(n\) 和操作的个数 \(q\) 。
第二行有 \(n\) 个整数,第 \(i\) 个整数表示序列中的第 \(i\) 个数 \(a_i\) 。
接下来 \(q\)
行,每行表示一个操作。每行首先有一个整数 \(op\) ,表示操作的类型。
若 \(op = 1\) ,则接下来有三个整数
\(l, r, x\) ,表示将区间 \([l, r]\) 内的每个数都修改为 \(x\) 。
若 \(op = 2\) ,则接下来有三个整数
\(l, r, x\) ,表示将区间 \([l, r]\) 内的每个数都加上 \(x\) 。
若 \(op = 3\) ,则接下来有两个整数
\(l, r\) ,表示查询区间 \([l, r]\) 内的最大值。
输出格式
对于每个 \(op = 3\)
的操作,输出一行一个整数表示答案。
样例 #1
样例输入 #1
1 2 3 4 5 6 7 8 6 6 1 1 4 5 1 4 1 1 2 6 2 3 4 2 3 1 4 3 2 3 1 1 6 -1 3 1 6
样例输出 #1
样例 #2
样例输入 #2
1 2 3 4 5 6 4 4 10 4 -3 -7 1 1 3 0 2 3 4 -4 1 2 4 -9 3 1 4
样例输出 #2
提示
数据规模与约定
对于 \(10\%\) 的数据,\(n = q = 1\) 。
对于 \(40\%\) 的数据,\(n, q \leq 10^3\) 。
对于 \(50\%\) 的数据,\(0 \leq a_i, x \leq 10^4\) 。
对于 \(60\%\) 的数据,\(op \neq 1\) 。
对于 \(90\%\) 的数据,\(n, q \leq 10^5\) 。
对于 \(100\%\) 的数据,\(1 \leq n, q \leq 10^6\) ,\(1 \leq l, r \leq n\) ,\(op \in \{1, 2, 3\}\) ,\(|a_i|, |x| \leq 10^9\) 。
提示
请注意大量数据读入对程序效率造成的影响。
其实还是懒标记的问题,只需要修改懒标记就行了
弄一个设置值的懒标记
和一个加的懒标记
设置值的初始化为正无穷
设置的加的懒标记为0
确认一下优先级
优先级肯定是设置值的高,因为一但设置,之前怎么加都是没用的
当我们要设置值的时候,set就设置为那个值,add清零
如果要加,康康设置有没有设置过(inf),如果没有,就可以添加到add,如果有,就添加到set那里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 #include <bits/stdc++.h> using namespace std;using ll = long long ;const ll N = 1e6 +10 ;ll inf = 1145141919810 ; ll lzy_add[N * 4 ]; ll lzy_set[N * 4 ]; int n,m;#define rep(i, a, n) for (int i = a; i <= n; i++) ll a[N]; ll w[N * 4 ]; void pushup (const int u) { w[u] = max (w[u <<1 ], w[u<<1 |1 ]); } void build (const int u, int l, int r) { lzy_set[u] = inf; if (l == r) { w[u] = a[l]; return ; } int mid = (l + r) /2 ; build (u * 2 , l, mid); build (u * 2 + 1 , mid + 1 , r); pushup (u); } bool inrange (int L, int R, int l, int r) { return (l <= L) && (R <= r); } bool outrange (int L, int R, int l, int r) { return (L > r) || (R < l); } void maketag (int u, ll x, int type) { if (type == 1 ) { lzy_add[u] = 0 ; lzy_set[u] = x; w[u] = x; } else { w[u] += x; if (lzy_set[u] != inf) { lzy_set[u] += x; } else { lzy_add[u] += x; } } } void pushdown (int u) { if (lzy_set[u] != inf) { maketag (u * 2 , lzy_set[u], 1 ); maketag (u * 2 + 1 , lzy_set[u], 1 ); lzy_set[u] = inf; } else { maketag (u * 2 , lzy_add[u], 2 ); maketag (u * 2 + 1 , lzy_add[u], 2 ); lzy_add[u] = 0 ; } } ll query (int u, int L, int R, int l, int r) { if (inrange (L, R, l, r)) { return w[u]; } else if (!outrange (L, R, l, r)) { int Mid = (L + R) / 2 ; pushdown (u); return max (query (u * 2 , L, Mid, l, r), query (u * 2 + 1 , Mid + 1 , R, l, r)); } else { return -inf; } } void update (int u, int L, int R, int l, int r, ll x, int type) { if (inrange (L, R, l, r)) { maketag (u, x, type); } else if (!outrange (L, R, l, r)) { int Mid = (L + R) / 2 ; pushdown (u); update (u * 2 , L, Mid, l, r, x, type); update (u * 2 + 1 , Mid + 1 , R, l, r, x, type); pushup (u); } } int main () { std::ios::sync_with_stdio (false ); std::cin.tie (0 ); std::cout.tie (0 ); cin >> n >> m; rep (i, 1 , n) { cin >> a[i]; } build (1 , 1 , n); rep (i, 1 , m) { int op, x, y; cin >> op; ll k; if (op == 1 ) { cin >> x >> y >> k; update (1 , 1 , n, x, y, k, 1 ); } else if (op == 2 ) { cin >> x >> y >> k; update (1 , 1 , n, x, y, k, 2 ); } else { cin >> x >> y; cout << query (1 , 1 , n, x, y) << '\n' ; } } }
【模板】线段树 2
题目描述
如题,已知一个数列,你需要进行下面三种操作:
将某区间每一个数乘上 \(x\) ;
将某区间每一个数加上 \(x\) ;
求出某区间每一个数的和。
输入格式
第一行包含三个整数 \(n,q,m\) ,分别表示该数列数字的个数、操作的总个数和模数。
第二行包含 \(n\)
个用空格分隔的整数,其中第 \(i\)
个数字表示数列第 \(i\) 项的初始值。
接下来 \(q\)
行每行包含若干个整数,表示一个操作,具体如下:
操作 \(1\) :
格式:1 x y k
含义:将区间 \([x,y]\) 内每个数乘上 \(k\)
操作 \(2\) :
格式:2 x y k
含义:将区间 \([x,y]\) 内每个数加上 \(k\)
操作 \(3\) :
格式:3 x y
含义:输出区间 \([x,y]\) 内每个数的和对 \(m\) 取模所得的结果
输出格式
输出包含若干行整数,即为所有操作 \(3\) 的结果。
样例 #1
样例输入 #1
1 2 3 4 5 6 7 5 5 38 1 5 4 2 3 2 1 4 1 3 2 5 1 2 4 2 2 3 5 5 3 1 4
样例输出 #1
提示
【数据范围】
对于 \(30\%\) 的数据:\(n \le 8\) ,\(q
\le 10\) 。
对于 \(70\%\) 的数据:$n ^3 \(,\) q ^4$。
对于 \(100\%\) 的数据:\(1 \le n \le 10^5\) ,\(1 \le q \le 10^5\) 。
除样例外,\(m = 571373\) 。
(数据已经过加强 _ )
样例说明:
故输出应为 \(17\) 、\(2\) (\(40 \bmod
38 = 2\) )。 好的,我帮你排版清晰一些,内容如下:
问题核心:懒标记如何处理
很明显,乘法 和加法 操作都会涉及懒标记。我们需要安排懒标记的
pushdown
顺序 。
假设两个懒标记分别为 mul
(乘法)和
add
(加法),根据顺序:
基础公式:
数值更新为:
$ + $
加入新的 add
懒标记时:
新数值变为:
$ + + $
加入新的 mul
懒标记时:
新数值变为:
$ + $
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 #include <bits/stdc++.h> using namespace std;#define rep(i, a, n) for (int i = a; i <= n; i++) using ll=long long ;const int N=1e6 +10 ;int p;ll a[N]; ll w[N<<2 ]; ll lzy_add[N<<2 ]; ll lzy_mul[N<<2 ]; void pushup (const int u) { w[u]=(w[u<<1 ]+w[u<<1 |1 ])%p; } void build (const int u,int l,int r) { lzy_mul[u]=1 ; if (l==r) { w[u]=a[l]; return ; } int mid=(l+r)>>1 ;build (u<<1 ,l,mid);build (u<<1 |1 ,mid+1 ,r);pushup (u);} bool Inrange (int L,int R,int l,int r) { return (l<=L&&r>=R); } bool outrange (int L,int R,int l,int r) { return (L>r)||(R<l); } void maketag (int u,int L,int R,ll x ,int type) { if (type==1 ) { (lzy_add[u]*=x)%=p; (lzy_mul[u]*=x)%=p; (w[u]*=x)%=p; } else { (lzy_add[u]+=x)%=p; (w[u]+=(R-L+1 )*x)%=p; } } void pushdown (int u,int L,int R) {int Mid=(L+R)/2 ;maketag (u<<1 ,L,Mid,lzy_mul[u],1 );maketag (u << 1 , L, Mid, lzy_add[u], 2 );maketag (u << 1 |1 , Mid+1 ,R, lzy_mul[u], 1 );maketag (u<<1 |1 ,Mid+1 ,R,lzy_add[u],2 );lzy_mul[u]=1 ; lzy_add[u]=0 ; } ll query (int u, int L, int R, int l, int r) { if (Inrange (L, R, l, r)) { return w[u]%p; } else if (!outrange (L, R, l, r)) { int Mid = (L + R) / 2 ; pushdown (u, L, R); return (((query (u * 2 , L, Mid, l, r))%p) + ((query (u * 2 + 1 , Mid + 1 , R, l, r))%p))%p; } else { return 0 ; } } void update (int u, int L, int R, int l, int r, ll x,int type) { if (Inrange (L, R, l, r)) { maketag (u, L,R, x,type); } else if (!outrange (L, R, l, r)) { int Mid = (L + R) / 2 ; pushdown (u, L, R); update (u <<1 , L, Mid, l, r, x,type); update (u<<1 |1 , Mid + 1 , R, l, r, x,type); pushup (u); } } int main () { std::ios::sync_with_stdio (false ); std::cin.tie (0 ); std::cout.tie (0 ); int n, m; cin >> n >> m>>p; rep (i, 1 , n) { cin >> a[i]; } build (1 ,1 ,n);int opt;rep (i,1 ,m){ cin>>opt; if (opt==1 ){ int x,y; ll z; cin>>x>>y>>z; update (1 ,1 ,n,x,y,z,1 ); } else if (opt==2 ){ int x, y; ll z; cin >> x >> y >> z; update (1 , 1 , n, x, y, z, 2 ); } else { int x,y; cin>>x>>y; cout<<query (1 ,1 ,n,x,y)%p<<'\n' ; } } }
小白逛公园
题目背景
小新经常陪小白去公园玩,也就是所谓的遛狗啦…
题目描述
在小新家附近有一条“公园路”,路的一边从南到北依次排着 \(n\)
个公园,小白早就看花了眼,自己也不清楚该去哪些公园玩了。
一开始,小白就根据公园的风景给每个公园打了分。小新为了省事,每次遛狗的时候都会事先规定一个范围,小白只可以选择第
\(a\) 个和第 \(b\) 个公园之间(包括 \(a, b\)
两个公园)选择连续的一些公园玩。小白当然希望选出的公园的分数总和尽量高咯。同时,由于一些公园的景观会有所改变,所以,小白的打分也可能会有一些变化。
那么,就请你来帮小白选择公园吧。
输入格式
第一行,两个整数 \(n\) 和 \(m\) ,分别表示表示公园的数量和操作(遛狗或者改变打分)总数。
接下来 \(n\)
行,每行一个整数,依次给出小白开始时对公园的打分。
接下来 \(m\)
行,每行三个整数。其中第一个整数 \(k\)
为 \(1\) 或 \(2\) 。
\(k=1\)
表示,小新要带小白出去玩,接下来的两个整数 \(a\) 和 \(b\) 给出了选择公园的范围 \((1 \le a,b \le n)\) 。测试数据可能会出现
\(a > b\)
的情况,需要进行交换;
\(k=2\)
表示,小白改变了对某个公园的打分,接下来的两个整数 \(p\) 和 \(s\) ,表示小白对第 \(p\) 个公园的打分变成了 \(s(1\le p\le N)\) 。
输出格式
小白每出去玩一次,都对应输出一行,只包含一个整数,表示小白可以选出的公园得分和的最大值。
样例 #1
样例输入 #1
1 2 3 4 5 6 7 8 9 5 3 1 2 -3 4 5 1 2 3 2 2 -1 1 2 3
样例输出 #1
提示
数据规模与约定
对于 \(100\%\) 的数据,\(1 \le n \le 5 \times 10^5\) ,\(1 \le m \le
10^5\) ,所有打分都是绝对值不超过 \(1000\) 的整数。
本题貌似不需要懒标记()
maxleft ,以左端点为头 的
和最大的连续序列。
maxright ,以右端点为尾 的
和最大的连续序列。
sum ,这段序列的总和
ans ,这段序列中的最大的连续序列
sum[k]=sum[k∗2]+sum[k∗2+1];
tree[k].maxleft=max(tree[k2].maxleft,tree[k2].sum+tree[k2+1].maxleft);//更新maxleft
tree[k].maxright=max(tree[k2+1].maxright,tree[k2].maxright+tree[k2+1].sum);//更新maxright
tree[k].ans=max(max(tree[k2].ans,tree[k2+1].ans),tree[k2].maxright+tree[k2+1].maxleft);//更新ans
这样就结束了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 #include <iostream> #include <cstdio> #define N 2000001 using namespace std;int n, m;struct Tree { int l, r; long long maxleft, maxright, sum,ans; }tree[N]; void pushup (int k) { tree[k].sum = tree[k * 2 ].sum + tree[k * 2 + 1 ].sum; tree[k].maxleft = max (tree[k * 2 ].maxleft, tree[k * 2 ].sum + tree[k * 2 + 1 ].maxleft); tree[k].maxright = max (tree[k * 2 + 1 ].maxright, tree[k * 2 ].maxright + tree[k * 2 + 1 ].sum); tree[k].ans = max (max (tree[k * 2 ].ans, tree[k * 2 + 1 ].ans), tree[k * 2 ].maxright + tree[k * 2 + 1 ].maxleft); } void build (int l, int r, int u) { tree[u].l = l; tree[u].r = r; if (l == r) { cin >> tree[u].sum; tree[u].maxleft = tree[u].maxright = tree[u].ans = tree[u].sum; return ; } int mid = (l + r)/2 ; build ( l, mid,u*2 ); build ( mid + 1 , r,u*2 +1 ); pushup (u); } Tree query (int u, int l, int r) { if (l <= tree[u].l && tree[u].r <= r) { return tree[u]; } int mid =(tree[u].l + tree[u].r)/2 ; if (r <= mid) { return query (u * 2 ,l, r); } else { if (l > mid) { return query (u * 2 + 1 , l, r); } else { Tree t, a = query (u * 2 , l, r), b = query (u * 2 + 1 , l, r); t.maxleft = max (a.maxleft, a.sum + b.maxleft); t.maxright = max (b.maxright, a.maxright + b.sum); t.ans = max (max (a.ans, b.ans), a.maxright + b.maxleft); return t; } } } void update (int u, int x, int y) { if (tree[u].l == tree[u].r) { tree[u].maxleft = tree[u].maxright = tree[u].ans = tree[u].sum = y; return ; } int mid = (tree[u].l + tree[u].r) / 2 ; if (x <= mid) { update (u * 2 , x, y); } else update (u * 2 + 1 , x, y); pushup (u); } int main () { cin >> n; cin >> m; build (1 , n, 1 ); while (m--) { int opt, x, y; cin >> opt >> x >> y; if (opt == 1 ) { if (x > y) { swap (x, y); } Tree t = query (1 , x, y); cout << t.ans << '\n' ; } else { update (1 , x, y); } } return 0 ; }
GSS3 - Can you answer
these queries III
题面翻译
\(n\) 个数,\(q\) 次操作
操作0 x y
把\(A_x\)
修改为\(y\)
操作1 l r
询问区间\([l,
r]\) 的最大子段和
感谢 @Edgration
提供的翻译
题目描述
You are given a sequence A of N (N <= 50000) integers between
-10000 and 10000. On this sequence you have to apply M (M <= 50000)
operations:
modify the i-th element in the sequence or for given x y print max{Ai +
Ai+1 + .. + Aj | x<=i<=j<=y }.
输入格式
The first line of input contains an integer N. The following line
contains N integers, representing the sequence A1..AN.
The third line contains an integer M. The next M lines contain the
operations in following form:
0 x y: modify Ax into y (|y|<=10000).
1 x y: print max{Ai + Ai+1 + .. + Aj | x<=i<=j<=y }.
输出格式
For each query, print an integer as the problem required.
样例 #1
样例输入 #1
1 2 3 4 5 6 7 4 1 2 3 4 4 1 1 3 0 3 -3 1 2 4 1 3 3
样例输出 #1
双倍经验啊!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 #include <iostream> #include <cstdio> using namespace std;const int N = 2000001 ;int n, m;struct Tree { int l, r; long long maxleft, maxright, sum,ans; }tree[N]; void pushup (int u) { tree[u].sum = tree[u*2 ].sum + tree[u *2 +1 ].sum; tree[u].maxleft = max (tree[u*2 ].maxleft, tree[u *2 ].sum + tree[u*2 +1 ].maxleft); tree[u].maxright = max (tree[u*2 +1 ].maxright, tree[u *2 ].maxright + tree[u *2 +1 ].sum); tree[u].ans = max (max (tree[u*2 ].ans, tree[u*2 +1 ].ans), tree[u*2 ].maxright + tree[u*2 +1 ].maxleft); } void build (int l, int r, int u) { tree[u].l = l; tree[u].r = r; if (l == r) { cin >> tree[u].sum; tree[u].maxleft = tree[u].maxright = tree[u].ans = tree[u].sum; return ; } int mid = (l + r)/2 ; build ( l, mid,2 *u); build ( mid + 1 , r,2 *u+1 ); pushup (u); } Tree query (int u, int l, int r) { if (l <= tree[u].l && tree[u].r <= r) { return tree[u]; } int mid =(tree[u].l + tree[u].r)/2 ; if (r <= mid) { return query (u * 2 ,l, r); } else { if (l > mid) { return query (u * 2 + 1 , l, r); } else { Tree t, a = query (u * 2 , l, r), b = query (u * 2 + 1 , l, r); t.maxleft = max (a.maxleft, a.sum + b.maxleft); t.maxright = max (b.maxright, a.maxright + b.sum); t.ans = max (max (a.ans, b.ans), a.maxright + b.maxleft); return t; } } } void update (int u, int x, int y) { if (tree[u].l == tree[u].r) { tree[u].maxleft = tree[u].maxright = tree[u].ans = tree[u].sum = y; return ; } int mid = (tree[u].l + tree[u].r) / 2 ; if (x <= mid) { update (u * 2 , x, y); } else update (u * 2 + 1 , x, y); pushup (u); } int main () { cin >> n; build (1 , n, 1 ); cin >> m; while (m--) { int opt, x, y; cin >> opt >> x >> y; if (opt == 1 ) { if (x > y) { swap (x, y); } Tree t = query (1 , x, y); cout << t.ans << '\n' ; } else { update (1 , x, y); } } return 0 ; }
逆序对
题目描述
猫猫 TOM 和小老鼠 JERRY
最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。
最近,TOM
老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中
\(a_i>a_j\) 且 \(i<j\)
的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。
Update:数据已加强。
输入格式
第一行,一个数 \(n\) ,表示序列中有
\(n\) 个数。
第二行 \(n\)
个数,表示给定的序列。序列中每个数字不超过 \(10^9\) 。
输出格式
输出序列中逆序对的数目。
样例 #1
样例输入 #1
样例输出 #1
提示
对于 \(25\%\) 的数据,\(n \leq 2500\)
对于 \(50\%\) 的数据,\(n \leq 4 \times 10^4\) 。
对于所有数据,\(n \leq 5 \times
10^5\)
请使用较快的输入输出
应该不会 \(O(n^2)\) 过 50 万吧 by
chen_zhe
归并排序,详见分治与倍增
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <bits/stdc++.h> using namespace std;using ll = long long ;#define rep(i, a, n) for (int i = a; i <= n; i++) #define frep(i, a, n) for (int i = a; i >= n; i--) const int N = 1e6 ;int n, a[N],t[N];ll ans; void merge_sort (int a[],int l,int r) { if (l==r) { return ; } int mid=l+r>>1 ;merge_sort (a,l,mid),merge_sort (a,mid+1 ,r);for (int i=l,j=l,k=mid+1 ;i<=r;i++){ if (j==mid+1 ) { t[i]=a[k++]; } else if (k==r+1 ) { t[i]=a[j++]; ans+=k-mid-1 ; } else if (a[j]<=a[k]) { t[i]=a[j++]; ans+=k-mid-1 ; } else { t[i]=a[k++]; } } for (int i = l; i <= r; i++){ a[i] = t[i]; } } int main () { cin>>n; rep (i,1 ,n) { cin>>a[i]; } merge_sort (a,1 ,n); cout<<ans; return 0 ; }
忠诚
题目描述
老管家是一个聪明能干的人。他为财主工作了整整 \(10\)
年。财主为了让自已账目更加清楚,要求管家每天记 \(k\)
次账。由于管家聪明能干,因而管家总是让财主十分满意。但是由于一些人的挑拨,财主还是对管家产生了怀疑。于是他决定用一种特别的方法来判断管家的忠诚,他把每次的账目按
\(1, 2, 3 \ldots\)
编号,然后不定时的问管家问题,问题是这样的:在 \(a\) 到 \(b\)
号账中最少的一笔是多少?为了让管家没时间作假,他总是一次问多个问题。
输入格式
输入中第一行有两个数 \(m,
n\) ,表示有 \(m\) 笔账 \(n\) 表示有 \(n\) 个问题。
第二行为 \(m\)
个数,分别是账目的钱数。
后面 \(n\) 行分别是 \(n\) 个问题,每行有 \(2\) 个数字说明开始结束的账目编号。
输出格式
在一行中输出每个问题的答案,以一个空格分割。
样例 #1
样例输入 #1
1 2 3 4 5 10 3 1 2 3 4 5 6 7 8 9 10 2 7 3 9 1 10
样例输出 #1
提示
对于 \(100\%\) 的数据,\(m \leq 10^5\) ,\(n \leq 10^5\) 。
ST表裸题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <bits/stdc++.h> using namespace std;const int N=1e5 +10 ;using ll=long long ;int st[N][20 ];int lg[N];int n,m;int main () { ios::sync_with_stdio (0 ); cin.tie (0 ); cout.tie (0 ); cin>>n>>m; lg[1 ]=0 ; for (int i=2 ;i<=n;i++) { lg[i]=lg[i>>1 ]+1 ; } for (int i=1 ;i<=n;i++) { cin>>st[i][0 ]; } for (int j=1 ;j<=lg[n];j++) { for (int i=1 ;i+(1 <<j)-1 <=n;i++) { st[i][j]=min (st[i][j-1 ],st[i+(1 <<(j-1 ))][j-1 ]); } } int l,r; while (m--) { cin>>l>>r; int t=lg[r-l+1 ]; cout<<min (st[l][t],st[r-(1 <<t)+1 ][t])<<' ' ; } }
方差
题目背景
滚粗了的 HansBug 在收拾旧数学书,然而他发现了什么奇妙的东西。
题目描述
蒟蒻 HansBug 在一本数学书里面发现了一个神奇的数列,包含 \(N\)
个实数。他想算算这个数列的平均数和方差。
输入格式
第一行包含两个正整数 \(N,M\) ,分别表示数列中实数的个数和操作的个数。
第二行包含 \(N\) 个实数,其中第
\(i\) 个实数表示数列的第 \(i\) 项。
接下来 \(M\)
行,每行为一条操作,格式为以下三种之一:
操作 \(1\) :1 x y k
,表示将第 \(x\) 到第 \(y\) 项每项加上 \(k\) ,\(k\)
为一实数。
操作 \(2\) :2 x y
,表示求出第 \(x\) 到第 \(y\) 项这一子数列的平均数。
操作 \(3\) :3 x y
,表示求出第 \(x\) 到第 \(y\) 项这一子数列的方差。
输出格式
输出包含若干行,每行为一个实数,即依次为每一次操作 \(2\) 或操作 \(3\) 所得的结果(所有结果四舍五入保留 \(4\) 位小数)。
样例 #1
样例输入 #1
1 2 3 4 5 6 7 5 5 1 5 4 2 3 2 1 4 3 1 5 1 1 1 1 1 2 2 -1 3 1 5
样例输出 #1
提示
关于方差:对于一个有 \(n\) 项的数列
\(A\) ,其方差 \(s^2\) 定义如下: \[s^2=\frac{1}{n}\sum\limits_{i=1}^n\left(A_i-\overline
A\right)^2\] 其中 \(\overline
A\) 表示数列 \(A\)
的平均数,\(A_i\) 表示数列 \(A\) 的第 \(i\) 项。
样例说明:
操作步骤
输入内容
操作要求
数列
输出结果
说明
\(0\)
-
-
1 5 4 2 3
-
-
\(1\)
2 1 4
求 \(\left[1,4\right]\) 内所有数字的平均数
1 5 4 2 3
3.0000
平均数 \(=\left(1+5+4+2\right)\div 4=3.0000\)
\(2\)
3 1 5
求 \(\left[1,5\right]\) 内所有数字的方差
1 5 4 2 3
2.0000
平均数 \(=\left(1+5+4+2+3\right)\div 5=3\) ,方差
\(=\left(\left(1-3\right)^2+\left(5-3\right)^2+\left(4-3\right)^2+\left(2-3\right)^2+\left(3-3\right)^2\right)\div
5=2.0000\)
\(3\)
1 1 1 1
将 \(\left[1,1\right]\) 内所有数字加 \(1\)
2 5 4 2 3
-
-
\(4\)
1 2 2 -1
将 \(\left[2,2\right]\) 内所有数字加 \(-1\)
2 4 4 2 3
-
-
\(5\)
3 1 5
求 \(\left[1,5\right]\) 内所有数字的方差
2 4 4 2 3
0.8000
平均数 \(=\left(2+4+4+2+3\right)\div 5=3\) ,方差
\(=\left(\left(2-3\right)^2+\left(4-3\right)^2+\left(4-3\right)^2+\left(2-3\right)^2+\left(3-3\right)^2\right)\div
5=0.8000\)
数据规模:
数据点
\(N\)
\(M\)
备注
\(1\sim3\)
\(N\le
8\)
\(M\le
15\)
-
\(4\sim7\)
\(N\le
10^5\)
\(M\le
10^5\)
不包含操作 \(3\)
\(8\sim10\)
\(N\le
10^5\)
\(M\le
10^5\)
-
image-20240227172501372
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 #include <iostream> using namespace std;double a[100010 ];struct node { double sum, lazy; }; node t1[400010 ], t2[400010 ]; void pushup (int rt) { t1[rt].sum = t1[rt << 1 ].sum + t1[rt << 1 | 1 ].sum; t2[rt].sum = t2[rt << 1 ].sum + t2[rt << 1 | 1 ].sum; } void build (int rt, int l, int r) { if (l == r) { cin>>t1[rt].sum; t2[rt].sum = t1[rt].sum * t1[rt].sum; return ; } int mid = (l + r) >> 1 ; build (rt << 1 , l, mid); build (rt << 1 | 1 , mid + 1 , r); pushup (rt); } void pushdown (int rt, int l, int r) { int mid = (l + r) >> 1 ; t2[rt << 1 ].lazy += t2[rt].lazy; t2[rt << 1 ].sum += t1[rt << 1 ].sum * (t2[rt].lazy * 2 ) + (mid - l + 1 ) * (t2[rt].lazy * t2[rt].lazy); t2[rt << 1 | 1 ].lazy += t2[rt].lazy; t2[rt << 1 | 1 ].sum += t1[rt << 1 | 1 ].sum * (t2[rt].lazy * 2 ) + (r - mid) * (t2[rt].lazy * t2[rt].lazy); t2[rt].lazy = 0 ; t1[rt << 1 ].lazy += t1[rt].lazy; t1[rt << 1 ].sum += (mid - l + 1 ) * t1[rt].lazy; t1[rt << 1 | 1 ].lazy += t1[rt].lazy; t1[rt << 1 | 1 ].sum += (r - mid) * t1[rt].lazy; t1[rt].lazy = 0 ; } bool inrange (int L,int R,int l,int r) { return (l<=L)&&(R<=r); } bool outrange (int L,int R,int l,int r) { return (L>r)||(R<l); } double query (node tree[], int rt, int l, int r, int x, int y) { if (inrange (l,r,x,y)) return tree[rt].sum; int mid = (l + r) >> 1 ; if (tree[rt].lazy) pushdown (rt, l, r); double ans = 0 ; if (x <= mid) ans = ans + query (tree, rt << 1 , l, mid, x, y); if (y > mid) ans = ans + query (tree, rt << 1 | 1 , mid + 1 , r, x, y); pushup (rt); return ans; } void modify (int rt, int l, int r, int x, int y, double z) { if (inrange (l,r,x,y)) { t2[rt].lazy += z; t2[rt].sum += t1[rt].sum * (z * 2 ) + (r - l + 1 ) * (z * z); t1[rt].lazy += z; t1[rt].sum += (r - l + 1 ) * z; return ; } else if (!outrange (l,r,x,y)) { int Mid=(l+r)/2 ; pushdown (rt,l,r); modify (rt*2 ,l,Mid,x,y,z); modify (rt*2 +1 ,Mid+1 ,r,x,y,z); pushup (rt); } } int n, m, opt;int main () { cin>>n>>m; build (1 , 1 , n); while (m--) { cin>>opt; if (opt == 1 ) { int x, y; double z; cin>>x>>y>>z; modify (1 , 1 , n, x, y, z); } if (opt == 2 ) { int x, y; cin>>x>>y; printf ("%.4lf\n" , query (t1, 1 , 1 , n, x, y) / ((y - x + 1 ) * 1.0 )); } if (opt == 3 ) { int x, y; cin>>x>>y; printf ("%.4lf\n" , (query (t2, 1 , 1 , n, x, y) / ((y - x + 1 ) * 1.0 )) - (query (t1, 1 , 1 , n, x, y) / ((y - x + 1 ) * 1.0 )) * (query (t1, 1 , 1 , n, x, y) / ((y - x + 1 ) * 1.0 ))); } } }
[COCI2010-2011#6] STEP
题目描述
给定一个长度为 \(n\) 的字符序列
\(a\) ,初始时序列中全部都是字符
L
。
有 \(q\) 次修改,每次给定一个 \(x\) ,若 \(a_x\) 为 L
,则将 \(a_x\) 修改成 R
,否则将 \(a_x\) 修改成 L
。
对于一个只含字符 L
,R
的字符串 \(s\) ,若其中不存在连续的 L
和
R
,则称 \(s\)
满足要求。
每次修改后,请输出当前序列 \(a\)
中最长的满足要求的连续子串的长度。
输入格式
第一行有两个整数,分别表示序列的长度 \(n\) 和修改操作的次数 \(q\) 。
接下来 \(q\)
行,每行一个整数,表示本次修改的位置 \(x\) 。
输出格式
对于每次修改操作,输出一行一个整数表示修改 \(a\) 中最长的满足要求的子串的长度。
样例 #1
样例输入 #1
样例输出 #1
样例 #2
样例输入 #2
样例输出 #2
提示
数据规模与约定
对于全部的测试点,保证 \(1 \leq n, q \leq 2
\times 10^5\) ,\(1 \leq x \leq
n\) 。
说明
题目译自 COCI2010-2011 CONTEST
#6 T5 STEP ,翻译来自 @一扶苏一 。
左右前缀区间维护
维护什么呢?
维护以下:
1 2 一个区间的前缀最大01 连续子串 一个区间的后缀最大01 连续子串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include <bits/stdc++.h> using namespace std;const int Len = 3e5 +100 ;int n,m,opt,a[Len],sum[Len << 2 ],lsum[Len << 2 ],rsum[Len << 2 ]; int ls (int x) { return x << 1 ; } int rs (int x) { return x << 1 | 1 ; } void push_up (int p,int l,int r) { int mid = (l + r) >> 1 ; int L = (mid - l + 1 ),R = r - mid; lsum[p] = lsum[ls (p)]; rsum[p] = rsum[rs (p)]; sum[p] = max (sum[ls (p)],sum[rs (p)]); if (a[mid] != a[mid + 1 ]) { sum[p] = max (sum[p],rsum[ls (p)] + lsum[rs (p)]); if (sum[ls (p)] == L) lsum[p] = L + lsum[rs (p)]; if (sum[rs (p)] == R) rsum[p] = R + rsum[ls (p)]; } } void build (int p,int l,int r) { sum[p] = lsum[p] = rsum[p] = 1 ; if (l == r) return ; int mid = (l + r) >> 1 ; build (ls (p),l,mid); build (rs (p),mid + 1 ,r); push_up (p,l,r); } void update (int p,int l,int r,int idx) { if (l == r && l == idx) { a[l] ^= 1 ; return ; } int mid = (l + r) >> 1 ; if (idx <= mid) update (ls (p),l,mid,idx); else update (rs (p),mid + 1 ,r,idx); push_up (p,l,r); } int query () { return sum[1 ]; } int main () { cin>>n>>m; build (1 ,1 ,n); while (m --) { cin>>opt; update (1 ,1 ,n,opt); cout<<query ()<<'\n' ; } return 0 ; }
三元上升子序列
题目描述
Erwin 最近对一种叫 thair
的东西巨感兴趣。。。
在含有 \(n\) 个整数的序列 \(a_1,a_2,\ldots,a_n\)
中,三个数被称作thair
当且仅当 \(i<j<k\) 且 \(a_i<a_j<a_k\) 。
求一个序列中 thair
的个数。
输入格式
开始一行一个正整数 \(n\) ,
以后一行 \(n\) 个整数 \(a_1,a_2,\ldots,a_n\) 。
输出格式
一行一个整数表示 thair
的个数。
样例 #1
样例输入 #1
样例输出 #1
样例 #2
样例输入 #2
样例输出 #2
提示
样例2 解释
\(7\) 个 thair
分别是:
1 2 3
1 2 4
1 2 3
1 2 4
1 3 4
2 3 4
2 3 4
数据规模与约定
对于 \(30\%\) 的数据 保证 \(n\le100\) ;
对于 \(60\%\) 的数据 保证 \(n\le2000\) ;
对于 \(100\%\) 的数据 保证 \(1 \leq n\le3\times10^4\) ,\(1\le a_i\leq 10^5\) 。
三元上升子序列的个数=每个数前比它小的数的个数*每个数后比它大的数的个数
之和
用线段树维护
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 #include <bits/stdc++.h> using namespace std;using ll=long long ;int n;ll ans; const int N=30001 ;struct node { int num,pos;}a[N]; int cnt;int num[2 *N];int sum[N*4 ];int s[2 *N];int b[2 *N];bool cmp (node a,node b) { return a.num<b.num; } void push_up (int root) { sum[root]=sum[root<<1 ]+sum[root<<1 |1 ]; } void update (int root,int l,int r,int pos) { if (l==r&&l==pos) { sum[root]++; return ; } int mid=(l+r)>>1 ; if (mid>=pos) { update (root*2 ,l,mid,pos); } if (mid<pos) { update (root*2 +1 ,mid+1 ,r,pos); } push_up (root); } int query (int root,int l,int r,int L,int R) { if (L<=l && r<=R) { return sum[root]; } int mid=(l+r)>>1 ; int t=0 ; if (mid>=L) t+=query (root*2 ,l,mid,L,R); if (mid<R) t+=query (root*2 +1 ,mid+1 ,r,L,R); return t; } int main () {cin>>n; for (int i=1 ;i<=n;i++){ cin>>a[i].num; a[i].pos=i; } sort (a+1 ,a+1 +n,cmp);for (int i=1 ;i<=n;i++) { if (a[i].num>a[i-1 ].num) cnt++; num[a[i].pos]=cnt; } for (int i=1 ;i<=n;i++){ if (num[i]>1 ) { s[i]=query (1 ,1 ,n,1 ,num[i]-1 ); } update (1 ,1 ,n,num[i]); } memset (sum,0 ,sizeof (sum)); for (int i=n;i>=1 ;i--) { if (num[i]<n) b[i]=query (1 ,1 ,n,num[i]+1 ,n); update (1 ,1 ,n,num[i]); } for (int i=1 ;i<=n;i++) { ans+=(b[i]*s[i]); } cout<<ans; }
色板游戏
题目背景
阿宝上学了,今天老师拿来了一块很长的涂色板。
题目描述
色板长度为 \(L\) ,\(L\)
是一个正整数,所以我们可以均匀地将它划分成 \(L\) 块 \(1\) 厘米长的小方格。并从左到右标记为 \(1, 2, \dots L\) 。
现在色板上只有一个颜色,老师告诉阿宝在色板上只能做两件事:
C A B C
指在 \(A\) 到
\(B\) 号方格中涂上颜色 \(C\) 。
P A B
指老师的提问:\(A\) 到 \(B\) 号方格中有几种颜色。
学校的颜料盒中一共有 \(T\)
种颜料。为简便起见,我们把他们标记为 \(1, 2,
\dots T\) . 开始时色板上原有的颜色就为 \(1\) 号色。
面对如此复杂的问题,阿宝向你求助,你能帮助他吗?
输入格式
第一行有3个整数 \(L (1 \le L \le 10^5), T
(1 \le T \le 30) 和 O (1 \le O \le 10^5)\) 。 在这里 \(O\) 表示事件数。
接下来 \(O\) 行, 每行以
C A B C
或 P A B
得形式表示所要做的事情(这里
\(A, B, C\) 为整数, 可能 \(A> B\) ,这样的话需要你交换 \(A\) 和 \(B\) )。
输出格式
对于老师的提问,做出相应的回答。每行一个整数。
样例 #1
样例输入 #1
1 2 3 4 5 2 2 4 C 1 1 2 P 1 2 C 2 2 2 P 1 2
样例输出 #1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 #include <bits/stdc++.h> using namespace std;const int T=31 ,N=100010 ;int n,t,m;int laz[T][N<<2 ],sum[T][N<<2 ];void pushup (int i,int x) { sum[i][x]=sum[i][x*2 ]+sum[i][x*2 +1 ]; } void build (int i,int x,int l,int r) { if (l==r) { sum[i][x]=1 ; return ; } int mid=l+r>>1 ; build (i,x*2 ,l,mid); build (i,x*2 +1 ,mid+1 ,r); pushup (i,x); } void pushdown (int i,int x) { if (laz[i][x]==-1 ) { sum[i][x*2 ]=sum[i][x*2 +1 ]=0 ; laz[i][x*2 ]=laz[i][x*2 +1 ]=-1 ; } else { sum[i][x*2 ]=sum[i][x*2 +1 ]=laz[i][x]; laz[i][x*2 ]=laz[i][x*2 +1 ]=laz[i][x]; } laz[i][x]=0 ; } void change (int i,int x,int l,int r,int ls,int rs,int v) { if (l>rs||r<ls) { return ; } if (l>=ls&&r<=rs) { sum[i][x]=v; if (!v) laz[i][x]=-1 ; else laz[i][x]=v; return ; } if (laz[i][x]) { pushdown (i,x); } int mid=l+r>>1 ; if (ls<=mid) { change (i,x*2 ,l,mid,ls,rs,v); } if (rs>mid) { change (i,x*2 +1 ,mid+1 ,r,ls,rs,v); } pushup (i,x); } int ask (int i,int x,int l,int r,int ls,int rs) { if (l>rs||r<ls) { return 0 ; } if (l>=ls&&r<=rs) { return sum[i][x]; } if (laz[i][x]) { pushdown (i,x); } int mid=l+r>>1 ; return { ask (i,x*2 ,l,mid,ls,rs)+ask (i,x*2 +1 ,mid+1 ,r,ls,rs); } } int main () { cin>>n>>t>>m; build (1 ,1 ,1 ,n); char s[3 ];int l,r,v; for (int i=1 ;i<=m;++i) { cin>>s>>l>>r; if (l>r) swap (l,r); if (s[0 ]=='C' ) { cin>>v; for (int k=1 ;k<=t;++k) if (k!=v) { change (k,1 ,1 ,n,l,r,0 ); } else { change (k,1 ,1 ,n,l,r,1 ); } } else { int ans=0 ; for (int k=1 ;k<=t;++k) if (ask (k,1 ,1 ,n,l,r)) { ++ans; } cout<<ans<<'\n' ; } } return 0 ; }
[yLOI2019] 棠梨煎雪
题目背景
岁岁花藻檐下共将棠梨煎雪,
自总角至你我某日辗转天边。
天淡天青,宿雨沾襟,
一年一会信笺却只见寥寥数言。
——银临《棠梨煎雪》
题目描述
扶苏正在听《棠梨煎雪》的时候,@spitfirekindergarten
发来一道 IOI2018
集训队作业题 :我切了这集训队题,辣鸡扶苏快过来做题。扶苏定睛一看,这不
s*
题嘛,写了一发交上去才发现自己看错题目了。但是写完的代码不能浪费,于是就有了这道题。
歌词中的主人公与她的朋友一年会有一次互相写信给对方,一共通信了 \(m\)
年。为了简化问题,我们认为她们每封信的内容都是一条二进制码,并且所有二进制码的长度都是
\(n\) 。即每封信的内容都是一个长度为
\(n\) 的字符串,这个字符串只含字符
0
或 1
。
这天她拿出了朋友写给她的所有信件,其中第 \(i\) 年的写的信件编号为 \(i\) 。由于信件保存时间过久,上面有一些字符已经模糊不清,我们将这样的位置记为
?
,?
字符可以被解释为 0
或
1
。由于她的朋友也是人,符合人类的本质,所以朋友在一段连续的时间中书写的内容可能是相同的。现在她想问问你,对于一段连续的年份区间
\([l,r]\)
中的所有信件,假如朋友在这段时间展示了人类的本质,所写的是同一句话,那么这一句话一共有多少种可能的组成。也即一共有多少字符串
\(S\) ,满足在这个区间内的所有信件的内容都可能是
\(S\) 。
一个长度为 \(n\) 的只含
0,1,?
的字符串 \(A\)
可能是一个字符串 \(B\) 当且仅当 \(B\) 满足如下条件:
\(B\) 的长度也是 \(n\) 。
\(B\) 中只含字符
0,1
。
\(A\) 中所有为 0
的位置在 \(B\) 中也是
0
。
\(A\) 中所有为 1
的位置在 \(B\) 中也是
1
。
\(A\) 中为 ?
的位置在
\(B\) 中可以为 0
也可以是
1
。
同时她可能会突然发现看错了某年的信的内容,于是她可能会把某一年的信的内容修改为一个别的只含
0
,1
,?
的长度为 \(n\) 的字符串。
输入格式
每个输入文件中都有且仅有一组测试数据。
输入数据第一行为三个用空格隔开的整数,分别代表代表字符串长度 \(n\) ,字符串个数 \(m\) 和操作次数 \(q\) 。
下面 \(m\) 行,每行是一个长度为
\(n\) 的字符串,第 \((i + 1)\) 行的字符串 \(s_i\) 代表第 \(i\) 年信的内容。
下面 \(q\)
行,每行的第一个数字是操作编号 \(opt\) 。
如果 \(opt=0\) ,那么后面接两个整数
\([l,~r]\) ,代表一次查询操作。
如果 \(opt=1\) ,那么后面接一个整数
\(pos\) ,在一个空格后会有一个长度为
\(n\) 的字符串 \(t\) ,代表将第 \(pos\) 个字符串修改为新的字符串 \(t\) 。
输出格式
为了避免输出过大,请你输出一行一个数代表所有查询的答案异或和对 \(0\) 取异或的结果。
样例 #1
样例输入 #1
1 2 3 4 5 6 7 8 9 3 3 5 010 0?0 1?0 0 1 2 0 2 3 1 3 0?? 0 2 3 0 1 3
样例输出 #1
提示
样例 1 解释
对于第一次询问,只有串 010
符合要求。
对于第二次询问,由于第二个串的第一位为
1
,第三个串的第一位为
0
,故没有串符合要求。
修改后将第三个串修改为 0??
。
对于第四次询问,有两个串符合要求,分别为 000
和
010
。
对于第五次询问,只有 010
符合要求。
故答案为 \(1,0,2,1\) ,他们的异或和再异或 \(0\) 的值为 \(2\) 。
数据规模与约定
本题采用多测试点捆绑测试,共有 7 个子任务 。
子任务编号
$m = $
$q = $
$n = $
子任务分数
\(1\)
\(1\)
\(0\)
\(1\)
\(5\)
\(2\)
\(102\)
\(102\)
\(10\)
\(10\)
\(3\)
\(1003\)
\(1003\)
\(10\)
\(15\)
\(4\)
\(1004\)
\(10004\)
\(30\)
\(15\)
\(5\)
\(100005\)
\(500005\)
\(1\)
\(15\)
\(6\)
\(100006\)
\(50006\)
\(30\)
\(10\)
\(7\)
\(100007\)
\(1000007\)
\(30\)
\(30\)
对于全部的测试点,保证:
\(1 \leq m \leq 10^5 + 7\) ,\(0 \leq q \leq 10^6 + 7\) ,\(1 \leq n \leq 30\) 。
\(0 \leq opt \leq 1\) ,\(1 \leq pos \leq m\) ,\(1 \leq l \leq r \leq m\) 。
\(s_i, t\) 的长度均为 \(n\) 且只含有字符
0
,1
,?
。
输入字符串的总长度不超过 \(5 \times
10^6\) 。数据在 Linux 下生成,即换行符不含 \r
。
提示
请注意常数因子对程序效率造成的影响。
请注意数据读入对程序效率造成的影响。
请注意输入的问号为嘤文问号,即其 ASCII 值为 \(63\)
注: 为减少错误做法的通过率,时限于 2020 年 7 月由 2000ms 改为
1500ms
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <bits/stdc++.h> #define maxn 100100 using ll= long long ;using namespace std;ll tree[maxn*4 ],a[maxn]; int fa[maxn],m,n,q,l,r,t;int find (int x) {return fa[x]==x?x:fa[x]=find (fa[x]);} void add (int x,ll y) {while (x<=n){ tree[x]+=y; x+=(x&-x); } } ll qry (int x) {ll r=0 ; while (x){ r+=tree[x]; x-=(x&-x); } return r;} int main () {cin>>n; for (int i=1 ;i<=n;i++){ cin>>a[i]; add (i,a[i]); fa[i]=i; } cin>>m; fa[n+1 ]=n+1 ; while (m--) { cin>>q>>l>>r; if (l>r) { swap (l,r); } if (q==1 ) { cout<<qry (r)-qry (l-1 )<<'\n' ; } else for (int i=l;i<=r;) { add (i,(t=sqrt (a[i]))-a[i]); a[i]=t; fa[i]=a[i]<=1 ?i+1 :i; i=fa[i]==i?i+1 :find (fa[i]); } } }
[SCOI2010] 序列操作
题目描述
lxhgww 最近收到了一个 \(01\)
序列,序列里面包含了 \(n\) 个数,下标从
\(0\) 开始。这些数要么是 \(0\) ,要么是 \(1\) ,现在对于这个序列有五种变换操作和询问操作:
0 l r
把 \([l, r]\)
区间内的所有数全变成 \(0\) ;
1 l r
把 \([l, r]\)
区间内的所有数全变成 \(1\) ;
2 l r
把 \([l,r]\)
区间内的所有数全部取反,也就是说把所有的 \(0\) 变成 \(1\) ,把所有的 \(1\) 变成 \(0\) ;
3 l r
询问 \([l, r]\)
区间内总共有多少个 \(1\) ;
4 l r
询问 \([l, r]\)
区间内最多有多少个连续的 \(1\) 。
对于每一种询问操作,lxhgww
都需要给出回答,聪明的程序员们,你们能帮助他吗?
输入格式
第一行两个正整数 \(n,m\) ,表示序列长度与操作个数。
第二行包括 \(n\)
个数,表示序列的初始状态。
接下来 \(m\)
行,每行三个整数,表示一次操作。
输出格式
对于每一个询问操作,输出一行一个数,表示其对应的答案。
样例 #1
样例输入 #1
1 2 3 4 5 6 7 8 9 10 11 12 10 10 0 0 0 1 1 0 1 0 1 1 1 0 2 3 0 5 2 2 2 4 0 4 0 3 6 2 3 7 4 2 8 1 0 5 0 5 6 3 3 9
样例输出 #1
提示
【数据范围】
对于 \(30\%\) 的数据,\(1\le n,m \le 1000\) ;
对于\(100\%\) 的数据,\(1\le n,m \le 10^5\) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 #include <bits/stdc++.h> using ll=long long ;#define rep(i, x, y) for(int i = (x);i <= (y);i++) using namespace std;const int N = 200019 ;int n,m;int v[N];#define lid (id << 1) #define rid (id << 1) | 1 struct seg_tree { int l, r; int sum; int lazy; int rev; int max[2 ], lmax[2 ], rmax[2 ]; }tree[N << 2 ]; void pushup (int id) { tree[id].sum = tree[lid].sum + tree[rid].sum; rep (i, 0 , 1 ) { tree[id].lmax[i] = tree[lid].lmax[i]; if (i == 1 && tree[lid].sum == tree[lid].r - tree[lid].l + 1 ) { tree[id].lmax[i] += tree[rid].lmax[i]; } else if (i == 0 && tree[lid].sum == 0 ) { tree[id].lmax[i] += tree[rid].lmax[i]; } tree[id].rmax[i] = tree[rid].rmax[i]; if (i == 1 && tree[rid].sum == tree[rid].r - tree[rid].l + 1 ) { tree[id].rmax[i] += tree[lid].rmax[i]; } else if (i == 0 && tree[rid].sum == 0 ) { tree[id].rmax[i] += tree[lid].rmax[i]; } tree[id].max[i] = tree[lid].rmax[i] + tree[rid].lmax[i]; tree[id].max[i] = max (tree[id].max[i], tree[lid].max[i]); tree[id].max[i] = max (tree[id].max[i], tree[rid].max[i]); } } void build (int id, int l, int r) { tree[id].l = l, tree[id].r = r, tree[id].lazy = -1 ; if (l == r) { tree[id].sum = v[l]; tree[id].max[0 ] = tree[id].lmax[0 ] = tree[id].rmax[0 ] = (v[l] == 0 ); tree[id].max[1 ] = tree[id].lmax[1 ] = tree[id].rmax[1 ] = (v[l] == 1 ); return ; } int mid = (l + r) >> 1 ; build (lid, l, mid), build (rid, mid + 1 , r); pushup (id); } void pushdown (int id) { if (tree[id].lazy != -1 ) { tree[id].rev = 0 ; int val = tree[id].lazy; tree[lid].sum = (tree[lid].r - tree[lid].l + 1 ) * val; tree[rid].sum = (tree[rid].r - tree[rid].l + 1 ) * val; tree[lid].lazy = tree[rid].lazy = val; tree[lid].rev = tree[rid].rev = 0 ; tree[lid].max[val] = tree[lid].lmax[val] = tree[lid].rmax[val] = tree[lid].r - tree[lid].l + 1 ; tree[lid].max[val ^ 1 ] = tree[lid].lmax[val ^ 1 ] = tree[lid].rmax[val ^ 1 ] = 0 ; tree[rid].max[val] = tree[rid].lmax[val] = tree[rid].rmax[val] = tree[rid].r - tree[rid].l + 1 ; tree[rid].max[val ^ 1 ] = tree[rid].lmax[val ^ 1 ] = tree[rid].rmax[val ^ 1 ] = 0 ; tree[id].lazy = -1 ; } if (tree[id].rev) { tree[lid].sum = (tree[lid].r - tree[lid].l + 1 ) - tree[lid].sum; tree[rid].sum = (tree[rid].r - tree[rid].l + 1 ) - tree[rid].sum; if (tree[lid].lazy != -1 )tree[lid].lazy ^= 1 ; else tree[lid].rev ^= 1 ; if (tree[rid].lazy != -1 )tree[rid].lazy ^= 1 ; else tree[rid].rev ^= 1 ; swap (tree[lid].max[0 ], tree[lid].max[1 ]); swap (tree[lid].lmax[0 ], tree[lid].lmax[1 ]); swap (tree[lid].rmax[0 ], tree[lid].rmax[1 ]); swap (tree[rid].max[0 ], tree[rid].max[1 ]); swap (tree[rid].lmax[0 ], tree[rid].lmax[1 ]); swap (tree[rid].rmax[0 ], tree[rid].rmax[1 ]); tree[id].rev = 0 ; } } void update (int id, int val, int l, int r) { pushdown (id); if (tree[id].l == l && tree[id].r == r) { if (val == 0 || val == 1 ) { tree[id].sum = (tree[id].r - tree[id].l + 1 ) * val; tree[id].lazy = val; tree[id].max[val] = tree[id].lmax[val] = tree[id].rmax[val] = tree[id].r - tree[id].l + 1 ; tree[id].max[val ^ 1 ] = tree[id].lmax[val ^ 1 ] = tree[id].rmax[val ^ 1 ] = 0 ; } else if (val == 2 ) { tree[id].sum = (tree[id].r - tree[id].l + 1 ) - tree[id].sum; tree[id].rev ^= 1 ; swap (tree[id].max[0 ], tree[id].max[1 ]); swap (tree[id].lmax[0 ], tree[id].lmax[1 ]); swap (tree[id].rmax[0 ], tree[id].rmax[1 ]); } return ; } int mid = (tree[id].l + tree[id].r) >> 1 ; if (mid < l)update (rid, val, l, r); else if (mid >= r)update (lid, val, l, r); else update (lid, val, l, mid), update (rid, val, mid + 1 , r); pushup (id); } int query (int id, int l, int r) { pushdown (id); if (tree[id].l == l && tree[id].r == r) { return tree[id].sum; } int mid = (tree[id].l + tree[id].r) >> 1 ; if (mid < l)return query (rid, l, r); else if (mid >= r)return query (lid, l, r); else return query (lid, l, mid) + query (rid, mid + 1 , r); } seg_tree Q_max (int id, int l, int r) { pushdown (id); if (tree[id].l == l && tree[id].r == r) return tree[id]; int mid = (tree[id].l + tree[id].r) >> 1 ; if (mid < l)return Q_max (rid, l, r); else if (mid >= r)return Q_max (lid, l, r); else { seg_tree ret, L = Q_max (lid, l, mid), R = Q_max (rid, mid + 1 , r); ret.sum = L.sum + R.sum; rep (i, 0 , 1 ){ ret.lmax[i] = L.lmax[i]; if (i == 1 && L.sum == L.r - L.l + 1 ) ret.lmax[i] += R.lmax[i]; else if (i == 0 && L.sum == 0 ) ret.lmax[i] += R.lmax[i]; ret.rmax[i] = R.rmax[i]; if (i == 1 && R.sum == R.r - R.l + 1 ) ret.rmax[i] += L.rmax[i]; else if (i == 0 && R.sum == 0 ) ret.rmax[i] += L.rmax[i]; ret.max[i] = L.rmax[i] + R.lmax[i]; ret.max[i] = max (ret.max[i], L.max[i]); ret.max[i] = max (ret.max[i], R.max[i]); } return ret; } } int main () { cin>>n>>m; rep (i, 1 , n)cin>>v[i]; build (1 , 1 , n); while (m--) { int cmd , l , r; cin>>cmd>>l>>r; l++, r++; if (cmd == 0 ) { update (1 , 0 , l, r); } else if (cmd == 1 ) { update (1 , 1 , l, r); } else if (cmd == 2 ) { update (1 , 2 , l, r); } else if (cmd == 3 ) { cout<<query (1 , l, r)<<'\n' ; } else { cout<<Q_max (1 , l, r).max[1 ]<<'\n' ; } } return 0 ; }