COGS 2638. 数列操作ψ 线段树

传送门 : COGS 2638. 数列操作ψ 线段树

这道题让我们维护区间最大值,以及维护区间and,or一个数
我们考虑用线段树进行维护,这时候我们就要用到吉司机线段树啦 QAQ

由于发现若干次and,or之后,如果数据分布均匀,那么几乎所有的数在若干次操作后都会变成同一个数
因为我们的and操作中的0位,以及or操作当中的1位,都是可以把整个区间的那一二进制位重置为相同的
我们考虑利用这一个性质
如果我们直接维护一个区间内的值是否是相同的,那么效果会差很多。
我们发现我们在进行and操作的时候只有为0的二进制位才可能更改原本的二进制位
同样的,在进行or操作的时候也只有为1的二进制位才可能更改原本的二进制位

所以我们可以在区间内所有的数的对应的会做出修改的二进制位完全相同时作出区间整体修改
至于区间整体修改,我们很容易发现,实际上就是区间内加上一个数

所以对于每一个线段树节点,维护一个sam值,表示这个线段树代表的区间内二进制位的相同情况
对应二进制位为1,则代表区间内所有值的这一位都是相同的

随后我们通过对区间内所有元素对应二进制位是否相同的情况来判断是否可以进行区间修改即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
inline void read(int &x){
    x=0;static char ch;static bool flag;flag = false;
    while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);++i)
const int maxn = 100010;
const int bas = 0x7fffffff;
int T[maxn<<2],lazy[maxn<<2],sam[maxn<<2];
int a[maxn];
inline void update(int rt){
    T[rt] = max(T[rt<<1],T[rt<<1|1]);
    sam[rt] = (sam[rt<<1]&sam[rt<<1|1]) & (~(T[rt<<1]^T[rt<<1|1]));
}
inline void pushdown(int rt){
    if(lazy[rt] == 0) return ;
    T[rt<<1] += lazy[rt];lazy[rt<<1] += lazy[rt];
    T[rt<<1|1] += lazy[rt];lazy[rt<<1|1] += lazy[rt];
    lazy[rt] = 0;
}
void build(int rt,int l,int r){
    if(l == r){
        T[rt] = a[l];
        sam[rt] = bas;
        return ;
    }int mid = l+r >> 1;
    build(rt<<1,l,mid);build(rt<<1|1,mid+1,r);
    update(rt);
}
int L,R,val,tmp;
inline bool check_a(int rt){
    tmp = (val^bas);
    return (tmp & sam[rt]) == tmp;
}
void modify_a(int rt,int l,int r){
    if(L <= l && r <= R && check_a(rt)){
        tmp = (T[rt] & val) - T[rt];
        lazy[rt] += tmp;T[rt] += tmp;
        return ;
    }int mid = l+r >> 1;pushdown(rt);
    if(L <= mid) modify_a(rt<<1,l,mid);
    if(R >  mid) modify_a(rt<<1|1,mid+1,r);
    update(rt);
}
void modify_o(int rt,int l,int r){
    if(L <= l && r <= R && (sam[rt] & val) == val){
        tmp = (T[rt] | val) - T[rt];
        lazy[rt] += tmp;T[rt] += tmp;
        return ;
    }int mid = l+r >> 1;pushdown(rt);
    if(L <= mid) modify_o(rt<<1,l,mid);
    if(R >  mid) modify_o(rt<<1|1,mid+1,r);
    update(rt);
}
int query(int rt,int l,int r){
    if(L <= l && r <= R) return T[rt];
    int mid = l+r >> 1;pushdown(rt);
    if(R <= mid) return query(rt<<1,l,mid);
    if(L >  mid) return query(rt<<1|1,mid+1,r);
    return max(query(rt<<1,l,mid),query(rt<<1|1,mid+1,r));
}
int main(){
    //freopen("series_wei.in","r",stdin);
    //freopen("series_wei.out","w",stdout);
    int n,m;read(n);read(m);
    rep(i,1,n) read(a[i]);
    build(1,1,n);int opt;
    int cnt = 0 ;
    while(m--){
        read(opt);read(L);read(R);
        if(opt == 1) read(val),modify_a(1,1,n);
        else if(opt == 2) read(val),modify_o(1,1,n);
        else printf("%d\n",query(1,1,n));
    }
    return 0;
}

原文地址:https://www.cnblogs.com/Skyminer/p/9185768.html

时间: 2024-11-08 20:41:52

COGS 2638. 数列操作ψ 线段树的相关文章

【COGS-2638】数列操作ψ 线段树

题目链接: http://cogs.pro/cogs/problem/problem.php?pid=2638 Solution 用jry推荐的写法即可做到单次$O(logN/log^{2}N)$. 具体的就是维护一个区间的$same$,其二进制下01的意义表示区间中所有数的二进制位第$k$位是否相同. 处理标记,当整个区间的 需要修改 的二进制位相同时即可直接打上标记. 然后就是正常搞了啊..其实当满足上述的情况后修改就可以变成区间加减标记来处理了,只需要一个标记即可,常数会更优一点. Cod

bzoj1789 AHOI 维护数列(线段树)

首先想到线段树,然后刚开始写忽然想到树状数组求和岂不是更快,而且编程复杂度又小,于是把之前写的删掉,写树状数组,写完模版之后忽然发现这题竟然是区间修改! 于是又删掉重写,忽然发现不会处理又加又乘的,果断看题解-- 经过几乎两个小时的调试,终于1A. 需要注意的是,一定要让线段树的每一个区间保存的值时刻为正确的,这样才能在调用时直接返回.比如说这道题的change和query操作的最后一句话: sum:=f(g[k<<1]+g[k<<1+1]) 而不是 sum:=f(t[k<&

BZOJ_4636_蒟蒻的数列_线段树+动态开点

Description 蒟蒻DCrusher不仅喜欢玩扑克,还喜欢研究数列 题目描述 DCrusher有一个数列,初始值均为0,他进行N次操作,每次将数列[a,b)这个区间中所有比k小的数改为k,他想知 道N次操作后数列中所有元素的和.他还要玩其他游戏,所以这个问题留给你解决. Input 第一行一个整数N,然后有N行,每行三个正整数a.b.k. N<=40000 , a.b.k<=10^9 Output 一个数,数列中所有元素的和 Sample Input 4 2 5 1 9 10 4 6

[bzoj4636]蒟蒻的数列_线段树

蒟蒻的数列 bzoj-4636 题目大意:给定一个序列,初始均为0.n次操作:每次讲一段区间中小于k的数都变成k.操作的最后询问全局和. 注释:$1\le n\le 4\cdot 10^4$. 想法:那个操作就是一个不好好说话的操作,说白了就是对区间的每一个数取max 然后我们对于那个序列建立分治线段树.每个操作我都把它挂在对应的log的点上. n个操作都执行完了之后我们从1号节点深搜,更新答案即可. 最后,附上丑陋的代码... ... #include <iostream> #include

无聊的数列【线段树】

题目背景 无聊的YYB总喜欢搞出一些正常人无法搞出的东西.有一天,无聊的YYB想出了一道无聊的题:无聊的数列...(K峰:这题不是傻X题吗) 题目描述 维护一个数列{a[i]},支持两种操作: 1.1 L R K D:给出一个长度等于R-L+1的等差数列,首项为K,公差为D,并将它对应加到a[L]~a[R]的每一个数上.即:令a[L]=a[L]+K,a[L+1]=a[L+1]+K+D, a[L+2]=a[L+2]+K+2D--a[R]=a[R]+K+(R-L)D. 2.2 P:询问序列的第P个数

bzoj 2962 序列操作(线段树)

题外话 做这道题我整个人都非常的绝望,推了一会发现是线段树裸题,然后调了N久一直是WA 情况是这样的 开始WA的几百毫秒的都是由于我比较SB造成的,可是跑了10几秒的程序我查了N久也查不出错 最后灵机一动把50000改成60000就过了,也不知道为啥T_T Description 一个长度为n的序列,有3种操作 1:区间加c 2:区间取为相反数 3:询问区间中选择c个数相乘的所有方案的和mod19940417的值 Solution 这个操作3非常鬼畜,似乎没啥好的办法,但是仔细推导一番会发现这个

bzoj 1858 序列操作(线段树)

题外话 本来想练练线段树的,然后发现这题及其蛋疼,要打一坨标记,这是我写过的最长的线段树了= = 然后我很SB的把R打成了r调了一个下午真是蛋疼QvQ Description: 给定一个0/1序列,有如下5个操作: 0:区间赋值为0 1:区间赋值为1 2:区间取反 3:询问区间内1的个数 4:询问区间内最大连续1的个数 Solution 没有操作4这显然就是个SB题,有了操作4我们需要打几个标记 我们需要从左端点开始连续0/1的个数,从右端点开始的连续0/1个数 区间内0/1个数,区间内最大连续

BZOJ 1858 SCOI2010 序列操作 线段树

题目大意:给定一个01序列,提供三种操作: 0:把一段区间的全部元素都变成0 1:把一段区间的全部元素都变成1 2:把一段区间内的全部元素全都取反 3:查询一段区间内1的个数 4:查询一段区间内最长的一段连续的1 首先假设没有操作4这就是bitset的水题... 多了这个,我们考虑线段树 线段树的每个节点存改动标记和翻转标记,以及该区间的信息 尽管查询的信息都是1 可是我们要连0一起保存 由于翻转后0就变成了1 1就变成了0 区间信息包含: 左端点的元素 右端点的元素 左端点開始的最长的连续元素

bzoj 2333 棘手的操作(线段树)

题外话 昨天粗去浪了一天,打麻将输了一下午真是拙计啊 这个题号2333真是2333,有爱无比 晚上回家把这题写了,1A,还算不错 Description 有N个节点,标号从1到N,这N个节点一开始相互不连通.第i个节点的初始权值为a[i],接下来有如下一些操作: U x y:加一条边,连接第x个节点和第y个节点 A1 x v:将第x个节点的权值增加v A2 x v:将第x个节点所在的连通块的所有节点的权值都增加v A3 v:将所有节点的权值都增加v F1 x:输出第x个节点当前的权值 F2 x: