-----------------------一起跃入人海,做一朵奔涌的浪花---------------------------
欢迎光临小店
问题 A: 数列分块入门1(区间加法,单点查询)
时间限制:
1
S
e
c
1 Sec
1Sec 内存限制:
256
M
B
256 MB
256MB
题目描述
给出一个长为
n
n
n的数列,以及
n
n
n 个操作,操作涉及区间加法,单点查值
输入
第一行输入一个数字
n
n
n。
第二行输入
n
n
n个数字,第i个数字为
a
i
ai
ai,以空格隔开。
接下来输入
n
n
n行询问,每行输入四个数字
o
p
t
opt
opt、
l
l
l、
r
r
r、
c
c
c,以空格隔开。
若
o
p
t
=
0
opt=0
opt=0,表示将位于
[
l
,
r
]
[l,r]
[l,r]的之间的数字都加c。
若
o
p
t
=
1
opt=1
opt=1,表示询问
a
r
ar
ar 的值(
l
l
l和
c
c
c忽略)。
输出
对于每次询问,输出一行一个数字表示答案。
样例输入
4
1 2 2 3
0 1 3 1
1 0 1 0
0 1 2 2
1 0 2 0
样例输出
2
5
提示
n
<
=
50000
,
−
2
31
<
=
o
t
h
e
r
s
,
a
n
s
<
=
2
31
−
1
n<=50000,-2^{31}<=others, ans <=2^{31}-1
n<=50000,−231<=others,ans<=231−1
解题思路:首先是
a
d
d
add
add操作,将
[
l
,
r
]
[l,r]
[l,r]之间的元素都加
c
c
c,对于整块的内容来说,直接维护一个
l
a
z
y
lazy
lazy标记即可,
l
a
z
y
lazy
lazy标记就记录这个整块中被
a
d
d
add
add了多少(注意不是记录加了多少次,是记录该整块中所有加的值的总和);而对于非整块来说,因为块内的元素并不是太多,暴力也是可以。接着就是
q
u
e
r
y
query
query操作,这里比较简单的就是,它是个单点查询,故查询函数的返回值就是
a
[
r
]
+
(
a
[
r
]
a[r]+(a[r]
a[r]+(a[r]所在块中的
l
a
z
y
lazy
lazy值)。
预处理和维护内容:先要把n给分成各个小块,经由其他大佬们的分析,分成每个块中最多放 s q r t ( n ) sqrt(n) sqrt(n)个元素的时间复杂是最小的,在这里我们就不必再多考虑了直接拿来用就是。用 b l o n g [ i ] blong[i] blong[i]来记录原数组第i个元素在第几个块中; L [ x ] L[x] L[x]记录第 x x x个块的左边界, R [ x ] R[x] R[x]记录第 x x x个块的右边界;当然也少不了去维护 l a z y lazy lazy数组啦, l a z y [ x ] lazy[x] lazy[x]记录的就是第 x x x块被 a d d add add了多少的值。
好,分析完毕,默默说一句,这道题用树状数组貌似是更快一点的。不过为了学习分块最好是用分块再来做一遍,当作入门中的入门,哈哈哈
上代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=50010;
int n,m,block,L[N],R[N],blong[N],num;
ll a[N],lazy[N];
/*
a[i]原数列元素
block块的大小
num块的个数
blong[i]表示属于哪一块
L[i]表示第i块的左边界
R[i]表示第i块的右边界
lazy[i]对第i块的懒惰标记
*/
void build()
{
block=sqrt(n);//每个块的大小,sqrt(n)时复杂度最低
if(n%block==0) num = n/block;
else num = n/block+1; //最后一个快可能不够block个
for(int i=1;i<=num;i++)
{
L[i]=(i-1)*block+1,R[i]=i*block;//每个块的左右边界
}
R[num] = n; //最后一块特殊处理
for(int i=1;i<=n;i++)
{
blong[i]=(i-1)/block + 1;//块的个数从1开始,So不应写成i/block
}
}
void add(int l,int r,ll c )
{
if(blong[l]==blong[r])//在同一块中,直接暴力处理
{
for(int i=l;i<=r;i++)
{
a[i]+=c;
}
return;
}
for(int i=l;i<=R[blong[l]];i++)//左侧不完整快
{
a[i]+=c;
}
for(int i=blong[l]+1;i<=blong[r]-1;i++)//处理中间的完整快
{
lazy[i] += c;
}
for(int i=L[blong[r]];i<=r;i++)//右侧不完整块
{
a[i]+=c;
}
}
ll query(int x)
{
return a[x]+lazy[blong[x]];
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
build();
m=n;
while(m--)
{
int op,l,r;
ll c;
scanf("%d%d%d%lld",&op,&l,&r,&c);
if(op==0)
{
add(l,r,c);
}
else{
printf("%lld\n",query(r));
}
}
return 0;
}
AC Coding:(更优雅的写法)
#include <cstdio>
#include <vector>
#include <cmath>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 50010;
ll a[N], add[N];
int len;
int get(int x) {
return (x - 1) / len;
}
void update(int l, int r, ll c) {
if (get(l) == get(r)) {
for (int i = l;i <= r;i++) a[i] += c;
}
else {
int i = l, j = r;
while (get(i) == get(l)) a[i++] += c;
while (get(j) == get(r)) a[j--] += c;
for (int k = get(i);k <= get(j);k++) add[k] += c;
}
}
ll query(int x) {
return a[x] + add[get(x)];
}
int main() {
int n; scanf("%d", &n);
for (int i = 1;i <= n;i++) scanf("%lld", &a[i]);
len = sqrt(n);
for (int i = 0;i < n;i++) {
char op[2];
int l, r;
ll c;
scanf("%s%d%d%lld", op, &l, &r, &c);
if (*op == '0') {
update(l, r, c);
}
else {
printf("%lld\n", query(r));
}
}
return 0;
}
问题 B: 数列分块入门 2(区间加,区间询问小于x的个数)
时间限制: 3 S e c 3 Sec 3Sec 内存限制: 256 M B 256 MB 256MB
题目描述
给出一个长为
n
n
n 的数列,以及
n
n
n 个操作,操作涉及区间加法,询问区间内小于某个值
x
x
x 的元素个数。
输入
第一行输入一个数字
n
n
n。
第二行输入
n
n
n 个数字,第
i
i
i 个数字为
a
i
ai
ai,以空格隔开。
接下来输入
n
n
n 行询问,每行输入四个数字
o
p
t
opt
opt 、
l
l
l、
r
r
r、
c
c
c,以空格隔开。
若
o
p
t
=
0
opt=0
opt=0,表示将位于
[
l
,
r
]
[l,r]
[l,r]的之间的数字都加
c
c
c。
若
o
p
t
=
1
opt=1
opt=1,表示询问中
[
l
,
r
]
[l,r]
[l,r]小于
c
2
c^2
c2的数字的个数。
输出
对于每次询问,输出一行一个数字表示答案。
样例输入
4
1 2 2 3
0 1 3 1
1 1 3 2
1 1 4 1
1 2 3 2
样例输出
3
0
2
提示
n
<
=
50000
,
−
2
31
<
=
o
t
h
e
r
s
,
a
n
s
<
=
2
31
−
1
n<=50000, -2^{31}<=others, ans<=2^{31}-1
n<=50000,−231<=others,ans<=231−1
解题思路:
首先我们来分析
q
u
e
r
y
query
query这个操作,我们查询的是一个区间内小于
c
2
c^2
c2的元素个数,对于非整块来说就是暴力查询即可,但对于整块来说,我们怎样才能更快的知道有多少个元素小于
c
2
c^2
c2呢?在这里我们来分析一下:如果这个整块是无序的,那我们就只能是暴力一个一个的判断是否符合条件,显然,在这么大的数据中,暴力是不可能让你过的;好,再来想一想,如果这个块是有序的,是不是就可以更快一点的得到答案呢,是可以的,有序后我们就可以用二分,二分的效率也是很高呐,正解也确实就是二分,这样的话,
q
u
e
r
y
query
query就给分析完毕了。
接着,来分析 a d d add add操作,和第一题的是相差不多的,唯独就是多了个给 q u e r y query query函数便捷的小通道,加了一个很好用 v e c t o r vector vector容器,作用是什么呢,那当然是对某个块进行排顺序所需咯。。(还有就是 S T L STL STL里的 l o w e r lower lower_ b o u n d bound bound函数可是要比手写二分简短的多呢)。。在我们更改序列中的元素后,对于整块的来说同时加上相同的元素,相对大小是不变的,也就是说序列顺序未曾改变;但对于非整块的来说,相当于在整块中把一部分元素给增加了,另一部分元素不变,此时这个块儿也就变为无序了。为了方便 q u e r y query query的进行,我们就需要再对这个块进行重新排序操作,至此, a d d add add函数差不多也都分析完了。
知晓上方我们想要的操作后,需要预处理的除了和第一题相同的 L [ x ] , R [ x ] , l a z y [ x ] , b l o n g [ i ] L[x],R[x],lazy[x],blong[i] L[x],R[x],lazy[x],blong[i]之外,我们需要把每一个快中的元素放入容器中,且在一开始就把已经放入容器中的元素进行排序。
OK,就这样,感觉我太罗嗦了,下方代码
上代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=50010;
int n,m,L[N],R[N],block,num,blong[N];
ll a[N],lazy[N];
vector<ll> v[N];
void build()
{
block=sqrt(n);//每个块的大小,sqrt(n)时复杂度最低
if(n%block==0) num = n/block;
else num = n/block + 1; //最后一个块可能不够block个
for(int i=1;i<=n;i++)
{
blong[i]=(i-1)/block + 1;//块的个数从1开始,So不应写成i/block
v[blong[i]].push_back(a[i]);
}
for(int i=1;i<=num;i++)
{
L[i]=(i-1)*block+1,R[i]=i*block;//每个块的左右边界
}
R[num] = n; //最后一块特殊处理
for(int i=1;i<=num;i++)
{
sort(v[i].begin(),v[i].end());
}
}
void resort(int x)//对x块进行排序
{
v[x].clear();
for(int i=L[x];i<=R[x];i++)
{
v[x].push_back(a[i]);
}
sort(v[x].begin(),v[x].end());
}
void update(int l,int r,ll c)
{
if(blong[l]==blong[r])
{
for(int i=l;i<=r;i++)
{
a[i]+=c;
}
resort(blong[l]);
return;
}
for(int i=l;i<=R[blong[l]];i++)
{
a[i]+=c;
}
for(int i=blong[l]+1;i<=blong[r]-1;i++)
{
lazy[i]+=c;
}
for(int i=L[blong[r]];i<=r;i++)
{
a[i]+=c;
}
resort(blong[l]);
resort(blong[r]);
}
int query(int l,int r,ll c)
{
int ans=0;
if(blong[l]==blong[r])
{
for(int i=l;i<=r;i++)
{
if(a[i]+lazy[blong[i]]<c)
ans++;
}
return ans;
}
for(int i=l;i<=R[blong[l]];i++)
{
if(a[i]+lazy[blong[i]]<c)
ans++;
}
for(int i=blong[l]+1;i<=blong[r]-1;i++)
{
//我么要查询的是a[i]+lazy[blong[i]]>=c的第一个位置(vector下标是从零开始的)
//移项后也就是a[i]>=c-lazy[blong[i]]啦,注意此a[i](指vector容器中的元素)非彼a[i](指原数组)
ans += lower_bound(v[i].begin(),v[i].end(),c-lazy[i])-v[i].begin();
}
for(int i=L[blong[r]];i<=r;i++)
{
if(a[i]+lazy[blong[i]]<c)
ans++;
}
return ans;
}
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline ll Read(){
ll x=0;
bool f=0;
char ch=getchar();
while (ch<'0'||'9'<ch) f|=ch=='-', ch=getchar();
while ('0'<=ch && ch<='9')
x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int main()
{
n=read();
for(int i=1;i<=n;i++) a[i]=Read();
build();
m=n;
while(m--)
{
int op,l,r;
ll c;
op=read(),l=read(),r=read();c=Read();
if(op==0) update(l,r,c);
else printf("%d\n",query(l,r,c*c));
}
return 0;
}
AC Coding:(更优雅的写法)
#include <cstdio>
#include <vector>
#include <cmath>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 50010;
ll a[N], add[N];
int len, n, L[N];
vector<ll> v[N];
int get(int x) {
return x / len;
}
void resort(int x)//将第x块重新排序
{
v[x].clear();
int i = L[x];
while (get(i) == x && i <= n) v[x].push_back(a[i++]);
sort(v[x].begin(), v[x].end());
}
void update(int l, int r, ll c) {
if (get(l) == get(r)) {
for (int i = l;i <= r;i++) a[i] += c;
resort(get(l));
}
else {
int i = l, j = r;
while (get(i) == get(l)) a[i++] += c;
while (get(j) == get(r)) a[j--] += c;
resort(get(l)), resort(get(r));
for (int k = get(i);k <= get(j);k++) add[k] += c;
}
}
int query(int l,int r,ll c) {
int res = 0;
if (get(l) == get(r)) {
for (int i = l;i <= r;i++) {
if (a[i] + add[get(i)] < c) res++;
}
}
else {
int i = l, j = r;
while (get(i) == get(l)) {
if (a[i++] + add[get(l)] < c) res++;
}
while (get(j) == get(r)) {
if (a[j--] + add[get(r)] < c) res++;
}
for (int k = get(i);k <= get(j);k++) {
res += lower_bound(v[k].begin(), v[k].end(), c - add[k]) - v[k].begin();
}
}
return res;
}
int main() {
scanf("%d", &n);
memset(L, 0x3f, sizeof L);
len = sqrt(n) + 1;
for (int i = 1;i <= n;i++) {
scanf("%lld", &a[i]);
int x = get(i);
L[x] = min(L[x], i);
v[x].push_back(a[i]);
}
for (int i = 0;i <= n / len;i++) {
sort(v[i].begin(), v[i].end());
}
for (int i = 0;i < n;i++) {
char op[2];
int l, r;
ll c;
scanf("%s%d%d%lld", op, &l, &r, &c);
if (*op == '0') update(l, r, c);
else printf("%d\n", query(l, r, c * c));
}
return 0;
}
问题 C: 数列分块入门 3(区间加,区间询问小于某个值的最大值)
时间限制: 3 S e c 3 Sec 3Sec 内存限制: 256 M B 256 MB 256MB
题目描述
给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间加法,询问区间内小于某个值 x x x 的前驱(比其小的最大元素)。
输入
第一行输入一个数字
n
n
n 。
第二行输入
n
n
n 个数字,第
i
i
i 个数字为
a
i
ai
ai,以空格隔开。
接下来输入 n 行询问,每行输入四个数字
o
p
t
opt
opt、
l
l
l、
r
r
r、
c
c
c,以空格隔开。
若
o
p
t
=
0
opt=0
opt=0,表示将位于[l,r]的之间的数字都加c。
若
o
p
t
=
1
opt=1
opt=1,表示询问[l,r]中c的前驱的值(不存在则输出
−
1
-1
−1)。
输出
对于每次询问,输出一行一个数字表示答案。
样例输入
4
1 2 2 3
0 1 3 1
1 1 4 4
0 1 2 2
1 1 2 4
样例输出
3
-1
提示
n
<
=
100000
,
−
2
31
<
=
o
t
h
e
r
s
,
a
n
s
<
=
2
31
−
1
n<=100000, -2^{31}<=others, ans<=2^{31}-1
n<=100000,−231<=others,ans<=231−1
解题思路:
这个题和上一个题类似,只是在查询的时候,上一个题用二分返回小于某个值的个数,这个题也是用到二分,不过返回的是小于某个值的最大值。其他操作和数列分块
2
2
2类似.
AC Coding:
#include <cstdio>
#include <vector>
#include <cmath>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 100010;
ll a[N], add[N];
int len, n, L[N];
vector<ll> v[N];
int get(int x) {
return x / len;
}
void resort(int x)//将第x块重新排序
{
v[x].clear();
int i = L[x];
while (get(i) == x && i <= n) v[x].push_back(a[i++]);
sort(v[x].begin(), v[x].end());
}
void update(int l, int r, ll c) {
if (get(l) == get(r)) {
for (int i = l;i <= r;i++) a[i] += c;
resort(get(l));
}
else {
int i = l, j = r;
while (get(i) == get(l)) a[i++] += c;
while (get(j) == get(r)) a[j--] += c;
resort(get(l)), resort(get(r));
for (int k = get(i);k <= get(j);k++) add[k] += c;
}
}
int query(int l,int r,ll c) {
ll res = -1;
if (get(l) == get(r)) {
for (int i = l;i <= r;i++) {
if (a[i] + add[get(i)] < c) res = max(res, a[i] + add[get(i)]);
}
}
else {
int i = l, j = r;
while (get(i) == get(l)) {
if (a[i++] + add[get(l)] < c) {
res = max(res, a[i - 1] + add[get(l)]);
}
}
while (get(j) == get(r)) {
if (a[j--] + add[get(r)] < c) {
res = max(res, a[j + 1] + add[get(r)]);
}
}
for (int k = get(i);k <= get(j);k++) {
int idx = lower_bound(v[k].begin(), v[k].end(), c - add[k]) - v[k].begin();
if (idx - 1 >= 0 && idx - 1 < v[k].size() && v[k][idx - 1] < c - add[k]) {
res = max(res, v[k][idx - 1] + add[k]);
}
}
}
return res;
}
int main() {
scanf("%d", &n);
memset(L, 0x3f, sizeof L);
len = sqrt(n) + 1;
for (int i = 1;i <= n;i++) {
scanf("%lld", &a[i]);
int x = get(i);
L[x] = min(L[x], i);
v[x].push_back(a[i]);
}
for (int i = 0;i <= n / len;i++) {
sort(v[i].begin(), v[i].end());
}
for (int i = 0;i < n;i++) {
char op[2];
int l, r;
ll c;
scanf("%s%d%d%lld", op, &l, &r, &c);
if (*op == '0') update(l, r, c);
else printf("%d\n", query(l, r, c));
}
return 0;
}
问题 D: 数列分块入门 4(区间加,区间求和)
时间限制:
1
S
e
c
1 Sec
1Sec 内存限制:
256
M
B
256 MB
256MB
题目描述
给出一个长为
n
n
n 的数列,以及
n
n
n 个操作,操作涉及区间加法,区间求和。
输入
第一行输入一个数字
n
n
n 。
第二行输入
n
n
n 个数字,第
i
i
i 个数字为
a
i
ai
ai,以空格隔开。
接下来输入
n
n
n 行询问,每行输入四个数字
o
p
t
opt
opt、
l
l
l、
r
r
r、
c
c
c,以空格隔开。
若
o
p
t
=
0
opt=0
opt=0,表示将位于
[
l
,
r
]
[l,r]
[l,r]的之间的数字都加
c
c
c。
若
o
p
t
=
1
opt=1
opt=1,表示询问
[
l
,
r
]
[l,r]
[l,r]中的所有数字的和
m
o
d
(
c
+
1
)
mod (c+1)
mod(c+1)
输出
对于每次询问,输出一行一个数字表示答案。
样例输入
4
1 2 2 3
0 1 3 1
1 1 4 4
0 1 2 2
1 1 2 4
样例输出
1
4
提示
n
<
=
50000
,
−
2
31
<
=
o
t
h
e
r
s
,
a
n
s
<
=
2
31
−
1
n<=50000, -2^{31}<=others, ans<=2^{31}-1
n<=50000,−231<=others,ans<=231−1
树状数组解题思路:
what?漏网之鱼呀,竟然没用分块再做一遍,回头得赶紧补上不过这个树状数组也是绝妙啊
哈哈,分块写法已补上。✌2021-08-17
来分析一波树状数组:树状数组的区间更改和区间求和
首先:我们知晓对于区间加和区间减,O(1)的操作就是维护一个原数组的差分数组就OK,关键就是区间求和。不难分析出,如果是单点求值的话很容易知道就是差分数组的前缀和,但是区间求和求的是差分数组的前缀和的前缀和,用公式来表示一下就是下方这个,其中b[i]指的是原数组a[i]的差分数组。
S
[
x
]
=
∑
i
=
1
x
∑
j
=
1
i
b
[
j
]
S[x]=\sum_{i=1}^{x}\sum_{j=1}^{i}b[j]
S[x]=i=1∑xj=1∑ib[j]
上代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 50010;
ll a[N],n;
//b[i]=a[i]-a[i-1]
ll tr1[N],tr2[N];//sum1用来维护 b[i]的前缀和,sum2用来维护i*b[i]的前缀和
ll lowbit(ll x)
{
return x & -x;
}
void add(ll tr[],ll x,ll c)
{
for(int i=x;i<=n;i+=lowbit(i))
{
tr[i]+=c;
}
}
ll getsum(ll tr[],ll x)
{
ll res=0;
for(int i=x;i;i-=lowbit(i))
{
res+=tr[i];
}
return res;
}
ll getans(ll x)
{
return (x+1)*getsum(tr1,x)-getsum(tr2,x);
}
int main()
{
scanf("%lld",&n);
ll m=n;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
ll c=a[i]-a[i-1];
add(tr1,i,c);
add(tr2,i,i*c);
}
while(m--)
{
ll op,l,r,c;
scanf("%lld%lld%lld%lld",&op,&l,&r,&c);
if(op==1)
{
printf("%lld\n",(getans(r)-getans(l-1))%(c+1));
}
else{
add(tr1,l,c),add(tr2,l,l*c);
add(tr1,r+1,-c),add(tr2,r+1,-c*(r+1));
}
}
return 0;
}
分块思路:
维护两个量,
a
d
d
[
x
]
add[x]
add[x]表示第
x
x
x块被加了多少,
s
u
m
[
x
]
sum[x]
sum[x]表示第
x
x
x块实际的和。
值得注意的是:要将
s
u
m
sum
sum初始化.
AC Coding:(分块代码)
#include <cstdio>
#include <vector>
#include <cmath>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 100010;
ll a[N], add[N], sum[N];
int len, n;
int get(int x) {
return (x - 1) / len;
}
void update(int l, int r, ll c) {
if (get(l) == get(r)) {
for (int i = l;i <= r;i++) a[i] += c, sum[get(l)] += c;
}
else {
int i = l, j = r;
while (get(i) == get(l)) a[i++] += c, sum[get(l)] += c;
while (get(j) == get(r)) a[j--] += c, sum[get(r)] += c;
for (int k = get(i);k <= get(j);k++) add[k] += c, sum[k] += 1ll * c * len;
}
}
ll query(int l,int r,ll c) {
ll res = 0;
if (get(l) == get(r)) {
for (int i = l;i <= r;i++) {
res += a[i] + add[get(l)];
}
}
else {
int i = l, j = r;
while (get(i) == get(l)) res += a[i++] + add[get(l)];
while (get(j) == get(r)) res += a[j--] + add[get(r)];
for (int k = get(i);k <= get(j);k++) {
res += sum[k];
}
}
return res;
}
int main() {
scanf("%d", &n);
len = sqrt(n) + 1;
for (int i = 1;i <= n;i++) {
scanf("%lld", &a[i]);
int x = get(i);
sum[x] += a[i];
}
for (int i = 0;i < n;i++) {
char op[2];
int l, r;
ll c;
scanf("%s%d%d%lld", op, &l, &r, &c);
if (*op == '0') update(l, r, c);
else {
c++;
ll res = query(l, r, c);
res = (res % c + c) % c;
printf("%lld\n", res);
}
}
return 0;
}
问题 E: 数列分块入门 5(区间开方,区间求和)
时间限制: 1 S e c 1 Sec 1Sec 内存限制: 128 M B 128 MB 128MB
题目描述
给出一个长为
n
n
n 的数列
a
1
a1
a1,
a
2
a2
a2,…,
a
n
an
an,以及
n
n
n 个操作,操作涉及区间开方,区间求和。
输入
第一行输入一个数字
n
n
n 。
第二行输入
n
n
n 个数字,第
i
i
i 个数字为
a
i
ai
ai,以空格隔开。
接下来输入
n
n
n 行询问,每行输入四个数字
o
p
t
opt
opt、
l
l
l、
r
r
r、
c
c
c,以空格隔开。
若
o
p
t
=
0
opt=0
opt=0,表示将位于
[
l
,
r
]
[l,r]
[l,r]的之间的数字都开平方(下取整)。
若
o
p
t
=
1
opt=1
opt=1,表示询问
[
l
,
r
]
[l,r]
[l,r]中所有数字的和。
输出
对于每次询问,输出一行一个数字表示答案。
样例输入
4
1 2 2 3
0 1 3 1
1 1 4 4
0 1 2 2
1 1 2 4
样例输出
6
2
提示
n
<
=
100000
,
−
2
31
<
=
o
t
h
e
r
s
,
a
n
s
<
=
2
31
−
1
n<=100000, -2^{31}<=others, ans<=2^{31}-1
n<=100000,−231<=others,ans<=231−1
先吐槽一波,1ll*(x)和(ll)x有啥区别吗,不都是强制类型转换嘛,为啥前者就非得害我debug几个小时不止呢,害,都是辛酸泪啊
切入正题,解题思路:
首先看数据范围最大是
2
31
−
1
2^{31}-1
231−1,在进行开方操作时,最多也就时开4~5次,就没必要开了,因为开和没开都是一样的,要么这个块中的元素要么为
0
0
0,要么为
1
1
1。所以对于这种开方前和开放后没啥区别的块直接跳过就
O
K
OK
OK,怎么判断是否可以跳过呢,其实这里设一个标记数组就好,
l
a
z
y
[
x
]
=
=
t
r
u
e
lazy[x]==true
lazy[x]==true表示这个块在进行开方的操作时可以直接跳过。
这道题多了一个维护区间和的数组
s
u
m
sum
sum,初始化的时候
s
u
m
[
x
]
sum[x]
sum[x]表示
x
x
x这个块中所有元素的和。每次开方,对于不完整的块是用暴力处理,对于完整的块,如果已经被标记,直接跳过,没被标记同样也是暴力处理(问复杂度的话,别问我,我也不知道,不会超就对了,能力限制,hh )
重头戏:就是怎样进行判断,是否要将这个块给标记呢?其实就是下方的这个代码,变量 t m p tmp tmp等于 0 0 0的话,相当于,开方前和开放后对 s u m sum sum并不产生影响,标记的就是这种块,对,就是这样。
for(int j=L[i];j<=R[i];j++)
{
tmp+=a[j]-(ll)sqrt(a[j]);
a[j]=sqrt(a[j]);
}
sum[i] -= tmp;
if(tmp==0) lazy[i]=true;
上代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=50010;
int n,m,L[N],R[N],block,num,blong[N];
ll a[N],sum[N];//sum用来记录小块的和
bool lazy[N];
void build()
{
block=sqrt(n);num=n/block; if(n%block) num++;
R[num]=n;
for(int i=1;i<=n;i++)
{
blong[i]=(i-1)/block+1;
}
for(int i=1;i<=num;i++)
{
L[i]=(i-1)*block+1,R[i]=i*block;
}
for(int i=1;i<=num;i++)
{
for(int j=L[i];j<=R[i];j++)
{
sum[i]+=a[j];
}
}
}
void update(int l,int r)
{
if(blong[l]==blong[r])
{
if(lazy[blong[l]]) return;
ll tmp=0;
for(int i=l;i<=r;i++)
{
tmp+=a[i]-(ll)sqrt(a[i]);
a[i]=sqrt(a[i]);
}
sum[blong[l]]-=tmp;
return;
}
ll tmp=0;
if(!lazy[blong[l]])
{
for(int i=l;i<=R[blong[l]];i++)
{
tmp+=a[i]-(ll)sqrt(a[i]);
a[i]=sqrt(a[i]);
}
}
sum[blong[l]]-=tmp;
for(int i = blong[l] + 1;i <= blong[r] - 1;i++)
{
tmp=0;
if(lazy[i]) continue;
else{
for(int j=L[i];j<=R[i];j++)
{
tmp+=a[j]-(ll)sqrt(a[j]);
a[j]=sqrt(a[j]);
}
sum[i] -= tmp;
if(tmp==0) lazy[i]=true;
}
}
tmp=0;
if(!lazy[blong[r]])
{
for(int i=L[blong[r]];i<=r;i++)
{
tmp+=a[i]-(ll)sqrt(a[i]);
a[i]=sqrt(a[i]);
}
}
sum[blong[r]]-=tmp;
}
ll query(int l,int r)
{
ll ans=0;
if(blong[l]==blong[r])
{
for(int i=l;i<=r;i++)
{
ans += a[i];
}
return ans;
}
for(int i=l;i<=R[blong[l]];i++)
{
ans+=a[i];
}
for(int i=blong[l]+1;i<=blong[r]-1;i++)
{
ans+=sum[i];
}
for(int i=L[blong[r]];i<=r;i++)
{
ans+=a[i];
}
return ans;
}
int main()
{
scanf("%d",&n);
memset(lazy,false,sizeof lazy);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
memset(sum,0,sizeof sum);
m=n;
build();
while(m--)
{
int op,l,r;
ll c;
scanf("%d%d%d%lld",&op,&l,&r,&c);
if(op==0) update(l,r);
else printf("%lld\n",query(l,r));
}
return 0;
}
AC Coding:(更优雅的写法)
#include <cstdio>
#include <vector>
#include <cmath>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 100010, M = 500;
ll a[N], sum[M];
bool flag[M];
int L[M], len, n;
int get(int x) {
return (x - 1) / len;
}
void update(int l, int r) {
if (get(l) == get(r)) {
for (int i = l;i <= r;i++) {
ll tmp = a[i] - sqrt(a[i]);
a[i] = sqrt(a[i]), sum[get(l)] -= tmp;
}
}
else {
int i = l, j = r;
while (get(i) == get(l)) {
ll tmp = a[i] - (ll)sqrt(a[i]);
a[i] = sqrt(a[i]), sum[get(l)] -= tmp;
i++;
}
while (get(j) == get(r)) {
ll tmp = a[j] - (ll)sqrt(a[j]);
a[j] = (ll)sqrt(a[j]), sum[get(r)] -= tmp;
j--;
}
for (int k = get(i);k <= get(j);k++) {
if (flag[k]) continue;
ll res = 0;
int t = L[k];
while (get(t) == k && t <= n) {
ll tmp = a[t] - (ll)sqrt(a[t]);
a[t] = (ll)sqrt(a[t]);
res += tmp;
sum[k] -= tmp;
t++;
}
if (res == 0) flag[k] = true;
}
}
}
ll query(int l,int r) {
ll res = 0;
if (get(l) == get(r)) {
for (int i = l;i <= r;i++) {
res += a[i];
}
}
else {
int i = l, j = r;
while (get(i) == get(l)) res += a[i++];
while (get(j) == get(r)) res += a[j--];
for (int k = get(i);k <= get(j);k++) {
res += sum[k];
}
}
return res;
}
int main() {
scanf("%d", &n);
len = sqrt(n) + 1;
memset(L, 0x3f, sizeof L);
for (int i = 1;i <= n;i++) {
scanf("%lld", &a[i]);
int x = get(i);
sum[x] += a[i];
L[x] = min(L[x], i);
}
for (int i = 0;i < n;i++) {
char op[2];
int l, r;
int c;
scanf("%s%d%d%d", op, &l, &r, &c);
if (*op == '0') update(l, r);
else {
ll res = query(l, r);
printf("%lld\n", res);
}
}
return 0;
}
问题 F: 数列分块入门 6(单点插入,单点查询)
时间限制: 1 S e c 1 Sec 1Sec 内存限制: 128 M B 128 MB 128MB
题目描述
出一个长为
n
n
n 的数列,以及
n
n
n 个操作,操作涉及单点插入,单点询问,数据随机生成。
输入
第一行输入一个数字
n
n
n 。
第二行输入
n
n
n 个数字,第
i
i
i 个数字为
a
i
ai
ai,以空格隔开。
接下来输入
n
n
n 行询问,每行输入四个数字
o
p
t
opt
opt、
l
l
l、
r
r
r、
c
c
c,以空格隔开。
若
o
p
t
=
0
opt=0
opt=0,表示在第
l
l
l 个数字前插入数字
r
r
r(
c
c
c忽略)。
若
o
p
t
=
1
opt=1
opt=1,表示询问
a
r
ar
ar 的值(
l
l
l和
c
c
c忽略)。
输出
对于每次询问,输出一行一个数字表示答案。
样例输入
4
1 2 2 3
0 1 3 1
1 1 4 4
0 1 2 2
1 1 2 4
样例输出
2
3
提示
n
<
=
100000
,
−
2
31
<
=
o
t
h
e
r
s
,
a
n
s
<
=
2
31
−
1
n<=100000, -2^{31}<=others, ans<=2^{31}-1
n<=100000,−231<=others,ans<=231−1
说一个搞笑的操作,我一开始想测试一下他的数据强不强,直接开动态数组,插入输出,太好笑了,哈哈哈,很幸运地得到了个T,嘿嘿嘿纯属无脑操作,不要学我像这样浪费时间哟。
暴力超时代码:
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int main()
{
vector<int> v;
v.reserve(200010);
int n;
cin >> n;
for (int i = 0;i < n;i++)
{
int x;
cin >> x;
v.push_back(x);
}
int m = n;
while(m--)
{
int op, l, r, c;
cin >> op >> l >> r >> c;
if (op == 0)
{
v.insert(v.begin() + l - 1, r);
/*for (int i = 0;i < v.size();i++)
{
cout << v[i] << " ";
}
puts("");*/
}
else cout << v[r - 1] << endl;
}
return 0;
}
在这里我就是想要放一下我的暴力代码,后来想了想它为啥会超时,这里我给简单的分析一下;首先来看我代码中的这一句
v.reserve(200010);
这一句的作用呢也是为了减少时间而写,了解过 v e c t o r vector vector的内存扩增原理的话,肯定会知道当 v e c t o r vector vector的内存不够用时,他就会以之前二分之三倍的内存进行扩增,扩增完毕之后再将原来的所有数据进行拷贝到新的内存中,并释放原有内存。这个过程是很耗费时间的,尤其是数据多的时候。而 r e s e r v e reserve reserve就很好的起到了在给定内存条件下不进行上述拷贝过程的作用,因为所有数据的内存并未超出初始化是的内存。( p s ps ps😃 想具体了解vector扩展内存的原理的小伙伴可以来这里学习点击学习 v e c t o r vector vector
显然这个对于在 v e c t o r vector vector尾部插入元素的话就相当于普通数组的操作了,即 O ( 1 ) O(1) O(1)的复杂度,但是新的问题来了,如果是插入在中间的话,那就会很麻烦,比如说在第 k k k个元素前插入一个元素,那么在第 k k k个元素之后(包括第 k k k个元素)的所有元素都要向后移动一位,这个复杂度显而易见是非常的大的,这也就是导致我上面暴力代码超时的主要原因了,至此,暴力代码分析完毕。
又感觉到我的这个暴力想法不是在浪费时间,我学到了呢,hh 😃 😃 😃 😃 😃 好玩儿,嘿嘿
解题思路(分块正解):
同样的,首先将整个区间分成多个小块,然后进行小块处理(感觉这个是废话,hh ).
对于插入操作的分析,我们可以先找到要插入的位置是属于第几块的第几个位置,这就是下方代码中
q
u
e
r
y
query
query函数的妙处了。这里有个需要注意的地方,那就是在测试数据中有可能存在在同一位置插入非常多次的操作,那样的话,当下次再处理这个非常多元素的块时是非常耗费时间的,所以就引入了新的操作,当插入的元素达到一定量时再一次对所有元素进行重新分块(至于这里我所说的“一定量”是多少,我也不是很会判断,想要具体了解的话,请直接到文末我所推的博文中去了解)。
对于查询操作,就是利用
q
u
e
r
y
query
query函数来找到
r
r
r是属于第几块第几个元素,最后输出这个元素即可。至此分块思路的分析完毕。
上代码:
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
typedef pair<int,int> PII;
int st[2*N],a[N],block,num,n,m;
vector<int> v[N];
PII query(int x)///返回某个位置是在第几个块的第几个位置
{
int k=1;
while(x > v[k].size())
{
x -= v[k].size(),k++;
}
return make_pair(k,x-1);///v中下标从零开始,即每个块的左边界都是0
}
void build()
{
block=sqrt(n);
num=n/block;if(n%block) num++;
for(int i=1;i<=n;i++)
{
int idx=(i-1)/block+1;
v[idx].push_back(a[i]);
}
}
void rebuild()
{
int s=0;
for(int i=1;i<=num;i++)
{
for(int j=0;j<v[i].size();j++)
{
st[++s]=v[i][j];
}
v[i].clear();
}
int block2=sqrt(s);
num=s/block2; if(s%block2) num++;
for(int i=1;i<=s;i++)
{
int idx=(i-1)/block+1;
v[idx].push_back(a[i]);
}
}
void insert(int x,int c)
{
PII tmp=quary(x);
v[tmp.first].insert(v[tmp.first].begin()+tmp.second,c);
if(v[tmp.first].size()>20*block) rebuild();
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
build();
m=n;
while(m--)
{
int op,l,r,c;
scanf("%d%d%d%d",&op,&l,&r,&c);
if(op==0) insert(l,r);
else{
PII it=query(r);
printf("%d\n",v[it.first][it.second]);
}
}
return 0;
}
AC Coding:(更优雅的写法)
#include <set>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
#define fi first
#define se second
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 100010;
int a[N], len;
vector<int> v[N];
int get(int x) {
return x / len;
}
PII query(int x) {
int k = 0;
while (x > v[k].size()) {
x -= v[k].size();
k++;
}
return make_pair(k, x - 1);
}
void insert(int x, int c) {
auto q = query(x);
v[q.fi].insert(v[q.fi].begin() + q.second, c);
}
void rebuild() {
vector<int> alls;
for (int i = 0;i < N;i++) {
if (v[i].size()) {
for (auto j : v[i]) {
alls.push_back(j);
}
}
else break;
v[i].clear();
}
len = sqrt(alls.size()) + 1;
for (int i = 0;i < alls.size();i++) {
int x = get(i);
v[x].push_back(alls[i]);
}
}
int main() {
int n; scanf("%d", &n);
len = sqrt(n) + 1;
int cnt = 0;
for (int i = 0;i < n;i++) {
scanf("%d", &a[i]);
int x = get(i);
v[x].push_back(a[i]);
}
for (int i = 0;i < n;i++) {
char op[2];
int l, r, c;
scanf("%s%d%d%d", op, &l, &r, &c);
if (*op == '0') {
cnt++;
insert(l, r);
if (cnt % len == 0) {
rebuild();
}
}
else {
auto t = query(r);
printf("%d\n", v[t.fi][t.se]);
}
}
return 0;
}
问题 G: 数列分块入门 7(区间乘,区间加,单点询问)
时间限制: 1 S e c 1 Sec 1Sec 内存限制: 256 M B 256 MB 256MB
题目描述
给出一个长为
n
n
n 的数列,以及
n
n
n 个操作,操作涉及区间乘法,区间加法,单点询问。
输入
第一行输入一个数字
n
n
n 。
第二行输入
n
n
n 个数字,第
i
i
i 个数字为
a
i
ai
ai,以空格隔开。
接下来输入
n
n
n 行询问,每行输入四个数字
o
p
t
、
l
、
r
、
c
opt、l、r、c
opt、l、r、c,以空格隔开。
若
o
p
t
=
0
opt=0
opt=0,表示将位于
[
l
,
r
]
[l,r]
[l,r]的之间的数字都加
c
c
c。
若
o
p
t
=
1
opt=1
opt=1,表示将位于
[
l
,
r
]
[l,r]
[l,r]之间的数字都乘
c
c
c。
若
o
p
t
=
2
opt=2
opt=2,表示询问
a
r
ar
ar的值
m
o
d
10007
mod 10007
mod10007(
l
l
l和
c
c
c忽略)
输出
对于每次询问,输出一行一个数字表示答案。
样例输入
7
1 2 2 3 9 3 2
0 1 3 1
2 1 3 1
1 1 4 4
0 1 7 2
1 2 6 4
1 1 6 5
2 2 6 4
样例输出
3
100
提示
n
<
=
100000
,
−
2
31
<
=
o
t
h
e
r
s
,
a
n
s
<
=
2
31
−
1
n<=100000, -2^{31}<=others, ans<=2^{31}-1
n<=100000,−231<=others,ans<=231−1
解题思路:
区间乘法和区间加法,欧呦,双管齐下呐,厉害。单独来看的话非常好操作就和数列分块1是类似的,不难操作。
这道题呢是两个操作的结合,一个懒标的话估计是不够,那么我们就来维护两个懒标,一个是维护乘法的懒标,一个是维护加法的懒标,接下来我们要找到这两个标记之间的联系;分析如下:
我们假设数组中某一元素的初始值为
x
x
x,经过入干次乘和加的操作后所得到的元素值为
N
N
N;
N
=
m
u
l
∗
x
+
a
d
d
N=mul*x+add
N=mul∗x+add
对于加的操作,就是
a
d
d
add
add加上相应的值就好;而对于乘的话,比如说乘的操作多了1,相应的
a
d
d
add
add也同样多了一倍,也就是说在
m
u
l
mul
mul增加了
k
k
k那么
a
d
d
add
add就要变为
k
∗
a
d
d
k*add
k∗add.
我解释的不太清楚,如果不懂的话也可以参考懒标之间的联系自我感觉这位大佬分析得非常清楚。
注意:在完整块中,对于整块的加法标记进行更新时,单独更新就好;对于乘法标记的更新,同时也要将加法标记连带上一起更新。在不完整的快中,需要暴力的将这个块中的所有元素的标记都给附加在数组元素中,也就是说给加的加,该乘的乘,把标记都给用完,之后在进行暴力更新不完整快中的元素。这里引入 p u s h d o w n pushdown pushdown函数,具体见代码。对于区域操作,那就很常见了,这里不过多解释,有关乘法的取余可参考我的另一篇博客取模运算(代码中有关于乘法取模的运算解释,欢迎来访)
上代码:
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
const int mod=10007;
int n,m,block,num,L[N],R[N],blong[N],a[N];
int add[N],mul[N];///维护加法标记和乘法标记
void build()
{
block=sqrt(n);
num=n/block; if(n%block) num++;
for(int i=1;i<=n;i++)
{
blong[i]=(i-1)/block+1;
}
for(int i=1;i<=num;i++)
{
L[i]=(i-1)*block+1,R[i]=i*block;
}
R[num]=n;
for(int i=1;i<=num;i++)
{
add[i]=0,mul[i]=1;
}
}
void pushdown(int x)
{
for(int i=L[x];i<=R[x];i++)
{
a[i]=((a[i]%mod*mul[x]%mod)%mod+add[x])%mod;
}
add[x]=0,mul[x]=1;
}
void update1(int l,int r,int c)
{
if(blong[l]==blong[r])
{
pushdown(blong[l]);
for(int i=l;i<=r;i++)
{
a[i] = (a[i] + c) % mod;
}
return;
}
pushdown(blong[l]);
for(int i=l;i<=R[blong[l]];i++)
{
a[i] = (a[i] + c) % mod;
}
for(int i=blong[l]+1;i<=blong[r]-1;i++)
{
add[i] += c;
}
pushdown(blong[r]);
for(int i=L[blong[r]];i<=r;i++)
{
a[i] = (a[i] + c) % mod;
}
}
void update2(int l,int r,int c)
{
if(blong[l]==blong[r])
{
pushdown(blong[l]);
for(int i=l;i<=r;i++)
{
a[i]=(a[i]%mod*c%mod)%mod;
}
return;
}
pushdown(blong[l]);
for(int i=l;i<=R[blong[l]];i++)
{
a[i]=(a[i]%mod*c%mod)%mod;
}
for(int i=blong[l]+1;i<=blong[r]-1;i++)
{
mul[i] = (mul[i]%mod * c%mod) % mod;
add[i] = (add[i]%mod * c%mod) % mod;
}
pushdown(blong[r]);
for(int i=L[blong[r]];i<=r;i++)
{
a[i]=(a[i]%mod*c%mod)%mod;
}
}
int query(int x)
{
return ((a[x]%mod*mul[blong[x]]%mod)%mod+add[blong[x]])%mod;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
build();
m=n;
while(m--)
{
int op,l,r,c;
scanf("%d%d%d%d",&op,&l,&r,&c);
if(op==0)
{
update1(l,r,c);
}
else if(op==1)
{
update2(l,r,c);
}
else
{
printf("%d\n",query(r));
}
}
return 0;
}
AC Coding:(更优雅的写法)
#include <cstdio>
#include <vector>
#include <cmath>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int mod = 10007;
const int N = 100010, M = 500;
ll a[N], add[M], mul[M];
int L[M], len, n;
int get(int x) {
return (x - 1) / len;
}
void pushdown(int x) {
int i = L[x];
while (get(i) == x) {
a[i] = a[i] * mul[x] + add[x];
a[i] %= mod;
i++;
}
add[x] = 0, mul[x] = 1;
}
void update1(int l, int r,ll c) //add
{
if (get(l) == get(r)) {
pushdown(get(l));
for (int i = l;i <= r;i++) {
a[i] += c;
a[i] %= mod;
}
}
else {
pushdown(get(l)), pushdown(get(r));
int i = l, j = r;
while (get(i) == get(l)) a[i] += c, a[i] %= mod, i++;
while (get(j) == get(r)) a[j] += c, a[j] %= mod, j--;
for (int k = get(i);k <= get(j);k++) {
add[k] += c;
add[k] %= mod;
}
}
}
void update2(int l, int r, ll c) //mul
{
if (get(l) == get(r)) {
pushdown(get(l));
for (int i = l;i <= r;i++) {
a[i] *= c;
a[i] %= mod;
}
}
else {
int i = l, j = r;
pushdown(get(l)), pushdown(get(r));
while (get(i) == get(l)) a[i] *= c, a[i] %= mod, i++;
while (get(j) == get(r)) a[j] *= c, a[j] %= mod, j--;
for (int k = get(i);k <= get(j);k++) {
add[k] *= c;
mul[k] *= c;
add[k] %= mod;
mul[k] %= mod;
}
}
}
ll query(int x) {
return (1ll * a[x] * mul[get(x)] % mod + add[get(x)]) % mod;
}
int main() {
scanf("%d", &n);
len = sqrt(n) + 1;
memset(L, 0x3f, sizeof L);
for (int i = 1;i <= n;i++) {
scanf("%lld", &a[i]);
int x = get(i);
mul[x] = 1;
L[x] = min(L[x], i);
}
for (int i = 0;i < n;i++) {
char op[2];
int l, r;
ll c;
scanf("%s%d%d%lld", op, &l, &r, &c);
if (*op == '0') update1(l, r, c);
else if (*op == '1') {
update2(l, r, c);
}
else{
ll res = query(r);
printf("%lld\n", res);
}
}
return 0;
}
问题 H: 数列分块入门 8(区间询问某个数的个数,区间修改)
时间限制: 1 S e c 1 Sec 1Sec 内存限制: 128 M B 128 MB 128MB
题目描述
给出一个长为
n
n
n 的数列,以及
n
n
n 个操作,操作涉及区间询问等于一个数
c
c
c 的元素,并将这个区间的所有元素改为
c
c
c 。
输入
第一行输入一个数字
n
n
n 。
第二行输入
n
n
n 个数字,第
i
i
i 个数字为
a
i
ai
ai,以空格隔开。
接下来输入
n
n
n 行询问,每行输入三个数字
l
、
r
、
c
l、r、c
l、r、c,以空格隔开。
表示先查询
[
l
,
r
]
[l,r]
[l,r]的数字有多少个是
c
c
c,再把位于
[
l
,
r
]
[l,r]
[l,r]的数字都改为
c
c
c
输出
对于每次询问,输出一行一个数字表示答案。
样例输入
4
1 2 2 4
1 3 1
1 4 4
1 2 2
1 4 2
样例输出
1
1
0
2
提示
n
<
=
100000
,
−
2
31
<
=
o
t
h
e
r
s
,
a
n
s
<
=
2
31
−
1
n<=100000, -2^{31}<=others, ans<=2^{31}-1
n<=100000,−231<=others,ans<=231−1
解题思路:
维护两个量,块标记
f
l
a
g
[
x
]
flag[x]
flag[x],
n
u
m
[
x
]
num[x]
num[x],如果第x块的所有元素都相同的话,就将第
x
x
x块的标记
f
l
a
g
[
x
]
flag[x]
flag[x]设为
t
r
u
e
true
true,
n
u
m
[
x
]
num[x]
num[x]设为那个相同的数,否则暴力处理。
AC Coding:
#pragma G++ optimize(2)
#pragma GCC optimize(2)
#include <set>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 100010, M = 500;
int a[N], len, n;
bool flag[M];//flag[x]标记第x块的所有数是否完全相同
int num[M],L[M],R[M];//num[x]在flag[x]为真时标记第x块的数是多少
inline int get(int x) {
return x / len;
}
void pushdown(int x) {
if (!flag[x]) return;
else {
for (int i = L[x];i <= R[x];i++) a[i] = num[x];
flag[x] = false;
num[x] = 1e9;
}
}
int query(int l,int r,int c) {
int res = 0;
if (get(l) == get(r)) {
pushdown(get(l));
for (int i = l;i <= r;i++) {
if (a[i] == c) res++;
else a[i] = c;
}
return res;
}
else {
int i = l, j = r;
pushdown(get(l)), pushdown(get(r));
while (get(i) == get(l)) {
if (a[i] == c) res++;
else a[i] = c;
i++;
}
while (get(j) == get(r)) {
if (a[j] == c) res++;
else a[j] = c;
j--;
}
for (int k = get(i);k <= get(j);k++) {
if (flag[k]) {
if (num[k] == c) res += len;
num[k] = c;
}
else{
for (int ii = L[k];ii <= R[k];ii++) {
if (a[ii] == c) res++;
else a[ii] = c;
}
flag[k] = true;
num[k] = c;
}
}
return res;
}
}
int main() {
scanf("%d", &n);
len = sqrt(n);
memset(L, 0x3f, sizeof L);
memset(flag, false, sizeof flag);
for (int i = 1;i <= n;i++) {
scanf("%d", &a[i]);
int x = get(i);
L[x] = min(L[x], i);
R[x] = max(R[x], i);
num[x] = 1e9;
}
for (int i = 1;i <= n;i++) {
int l, r, c; scanf("%d%d%d", &l, &r, &c);
printf("%d\n", query(l, r, c));
}
return 0;
}
问题 I: 数列分块入门 9(区间询问最小众数)
时间限制: 3 S e c 3 Sec 3Sec 内存限制: 256 M B 256 MB 256MB
题目描述
给出一个长为
n
n
n的数列,以及
n
n
n 个操作,操作涉及询问区间的最小众数。
输入
第一行输入一个数字
n
n
n 。
第二行输入
n
n
n 个数字,第
i
i
i 个数字为
a
i
ai
ai,以空格隔开。
接下来输入
n
n
n 行询问,每行输入两个数字
l
、
r
,
l、r,
l、r,以空格隔开。
表示查询位于
[
l
,
r
]
[l,r]
[l,r]的数字的众数。
输出
对于每次询问,输出一行一个数字表示答案。
样例输入
4
1 2 2 4
1 2
1 4
2 4
3 4
样例输出
1
2
2
2
提示
n
<
=
100000
,
−
2
31
<
=
o
t
h
e
r
s
,
a
n
s
<
=
2
31
−
1
n<=100000, -2^{31}<=others, ans<=2^{31}-1
n<=100000,−231<=others,ans<=231−1
解题思路:
知识点:离散化+莫队+分块
刚学会了回滚莫队,这直接就是一回滚莫队模板题呐。顺势来补上。
数据范围有点大,需要离散化。
将所有询问按双关键字排序,先按左端点分块编号从小到大排序,块号相同按右端点排序。
维护一个出现的最大次数和最小众数。
从前往后,一个块一个块的处理。只需要加元素,无需删元素。
AC Coding:
#include <cstdio>
#include <vector>
#include <cmath>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 100010;
vector<int> v;
int a[N], len, ans[N], cnt[N];
struct Query {
int id, l, r;
}q[N];
int get(int x) {
return x / len;
}
bool cmp(Query x, Query y) {
int i = get(x.l), j = get(y.l);
if (i != j) return i < j;
else return x.r < y.r;
}
void add(int x, int& maxcnt, int& res) {
cnt[x]++;
if (cnt[x] > maxcnt) res = v[x], maxcnt = cnt[x];
else if (cnt[x] == maxcnt && v[x] <= res) res = v[x];
}
int main() {
int n; scanf("%d", &n);
len = sqrt(n);
for (int i = 1;i <= n;i++) scanf("%d", &a[i]), v.push_back(a[i]);
sort(v.begin(), v.end());
v.erase(unique(v.begin(), v.end()), v.end());
for (int i = 1;i <= n;i++) {
a[i] = lower_bound(v.begin(), v.end(), a[i]) - v.begin();
}
for (int i = 0;i < n;i++) {
int l, r; scanf("%d%d", &l, &r);
q[i] = { i,l,r };
}
sort(q, q + n, cmp);
for (int x = 0;x < n;) {
int y = x;
while (y < n && get(q[y].l) == get(q[x].l)) y++;
int right = get(q[x].l) * len + len - 1;
while (x < y && q[x].r <= right) {
//cout << "case:" << 1 << endl;
int maxcnt = 0, res = 0x3f3f3f3f;
int id = q[x].id, l = q[x].l, r = q[x].r;
for (int k = l;k <= r;k++) add(a[k], maxcnt, res);
ans[id] = res;
for (int k = l;k <= r;k++) cnt[a[k]]--;
x++;
}
int maxcnt = 0, res = 0x3f3f3f3f;
int i = right, j = right + 1;
while (x < y) {
int id = q[x].id, l = q[x].l, r = q[x].r;
//cout << "query:" << id << endl;
while (i < r) add(a[++i], maxcnt, res);
int tmpcnt = maxcnt, tmpres = res;
//cout << tmpcnt << " " << tmpres << endl;
while (j > l) add(a[--j], maxcnt, res);
ans[id] = res;
maxcnt = tmpcnt, res = tmpres;
while (j < right + 1) cnt[a[j++]]--;
x++;
}
memset(cnt, 0, sizeof cnt);
}
for (int i = 0;i < n;i++) printf("%d\n", ans[i]);
return 0;
}
参考文献巨巨
现更于2020年6月1日,儿童节快乐,撒花撒花撒花,✌
完结。最近更新于2021年8月17日。