原题链接:
序列 - 洛谷https://www.luogu.com.cn/problem/P7492
题目背景
disangan233 正在数数,他希望你帮他记录数数的序列,并完成一些操作。
题目描述
你有一个长为 n 的序列 a,现在要对其进行 m 次操作。操作分为两种:
- 给定两个整数 l,r,表示询问 l 到 r 的最大连续子段和。
- 给定三个整数 l,r,k,表示将 l 到 r 的 ai 都按位或上一个 k。
对于所有数据,n,m≤10^5,−2^30≤ai,k<2^30,1≤l≤r≤n。
注意:负数按照 32 位补码取按位或。
输入格式
输入共 m+2 行。
第 1 行输入 2 个正整数 n,m。
第 2 行输入 n 个整数 a1…an。
接下来 m 行,每行先输入 1 个正整数 op,op=1 输入 2 个整数 l,r 表示一次询问,否则输入 3 个整数 l,r,k 表示一次修改。
输出格式
输出共若干行,每行共 1 个整数,表示询问的答案。
输入输出样例
输入 #1复制
15 15 512 -65 33554432 32 8194 13 16 2 67108872 131072 -8192 8194 16 2048 4096 1 3 5 1 10 10 2 1 7 671367424 1 8 14 1 5 11 2 13 13 335579137 2 2 13 5376 1 2 5 2 5 6 8392768 1 1 2 2 2 14 201335872 2 1 14 0 1 11 12 1 8 12 1 4 9
输出 #1复制
33562658 131072 67242012 2081350441 2047680290 671367936 201340226 805489228 3373416393
思路:
如果你还不知道线段树是啥可以先做:【模板】线段树 1 - 洛谷https://www.luogu.com.cn/problem/P3372
【模板】线段树 2 - 洛谷https://www.luogu.com.cn/problem/P3373 如果你还不会线段树求最大子段和,可以先做:
但注意:这题需要注册spoj账号并绑定才可以交,,spoj账号需要代理才能注册GSS1 - Can you answer these queries I - 洛谷https://www.luogu.com.cn/problem/SP1043
本题在修改过程中如果使用暴力单点或操作肯定是会超时,那么该怎么样操作才可以使时间复杂度降下来呢
因为按位或的性质,已经变成1的位置无论怎么或都不可能再改变为0了。那么已经被或为全1的数,无论再怎么或也是无意义的或了.但是除了这样还是不够的。
那么还有什么情况是属于无意义的或操作呢,将一个数拆分为二进制数
如 十进制数 114514
转换为二进制之后为
11011111101010010
我们发现,如果一个数能将其0位变成1位则才是有意义的或操作
我们可以使用一个标记数opt为该数取反之后的数,则若将要被或上的数&opt为0,说明这个将要被或上的数没有办法填补该数的任何一个“坑位”,那自然就是无意义的或了
opt的pushup:
在下放将要被或上的数x时,只要左孩子或右孩子有坑位,就自然是可以或上的,那么当前结点的坑位自然就是左孩子的opt | 右孩子的opt
我们以左右孩子为叶子结点举例假设左孩子的数为114514,右孩子的数为110550,那么左孩子的二进制数为11011111101010010,右孩子的二进制数为11010111111010110
则:
左孩子的opt:00100000010101101
右孩子的opt:00101000000101001
父结点的opt(左孩子opt|右孩子opt):00101000010101101
可以明显看出,父结点完美继承了左右孩子的"坑位"
而剩余的就是求线段树最大子段和了
ACcode:
#include<bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
inline ll read()
{
ll f = 1, x = 0; char c = getchar();
while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar();}
return f * x;
}
struct node{
ll ans,lans,rans,sum,opt;
node(){
ans = lans = rans = sum = opt = 0;
}
}t[400005];
int a[100005];
int n,m,l,r,x;
void pushup(int now)
{
t[now].sum = t[now<<1].sum+t[now<<1|1].sum;
t[now].lans = max(t[now<<1].lans,t[now<<1].sum+t[now<<1|1].lans);
t[now].rans = max(t[now<<1|1].rans,t[now<<1|1].sum+t[now<<1].rans);
t[now].ans = max(max(t[now<<1].ans,t[now<<1|1].ans),t[now<<1].rans+t[now<<1|1].lans);
t[now].opt = t[now<<1].opt|t[now<<1|1].opt;//继承左右孩子的坑位
}
void build(int start,int end,int now)
{
if(start==end)
{
t[now].ans = t[now].lans = t[now].rans = max(0ll,a[start]);
t[now].sum = a[start];
t[now].opt = ~a[start];
return;
}
int mid = (start+end)>>1;
build(start,mid,now<<1);
build(mid+1,end,now<<1|1);
pushup(now);
}
void update(int start,int end,int now)
{
if(l<=start&&r>=end)
{
if(!(t[now].opt&x)) return;//在本结点已经没有x需要或的坑位了
if(start==end)//已经到叶子结点了
{
a[start]|=x;
t[now].ans = t[now].lans = t[now].rans = max(0ll,a[start]);
t[now].sum = a[start];
t[now].opt = ~a[start];
return;
}
int mid = (start+end)>>1;
if(t[now<<1].opt&x) update(start,mid,now<<1);//如果左孩子有x的坑位就下放
if(t[now<<1|1].opt&x) update(mid+1,end,now<<1|1);//如果右孩子有x的坑位就下放
pushup(now);
return;
}
int mid = (start+end)>>1;
if(l<=mid&&t[now<<1].opt&x) update(start,mid,now<<1);
if(r>mid&&t[now<<1|1].opt&x) update(mid+1,end,now<<1|1);
pushup(now);
}
node query(int start,int end,int now)
{
if(l<=start&&r>=end)
return t[now];
int mid = (start+end)>>1;
if(l>mid) return query(mid+1,end,now<<1|1);
else if(r<=mid) return query(start,mid,now<<1);
else{
node aa = query(start,mid,now<<1),bb = query(mid+1,end,now<<1|1);
node aans;
aans.sum = aa.sum+bb.sum;
aans.lans = max(aa.lans,aa.sum+bb.lans);
aans.rans = max(aa.rans+bb.sum,bb.rans);
aans.ans = max(max(aa.ans,bb.ans),aa.rans+bb.lans);
return aans;
}
}
signed main()
{
n = read(),m=read();
for(int i = 1;i<=n;++i)
a[i] = read();
build(1,n,1);
while(m--)
{
int o = read();
if(o==1)
{
l = read();
r = read();
printf("%lld\n",query(1,n,1).ans);
}
else
{
l = read();
r = read();
x = read();
update(1,n,1);
}
}
return 0;
}