bzoj 2962 序列操作(线段树)

题外话


做这道题我整个人都非常的绝望,推了一会发现是线段树裸题,然后调了N久一直是WA

情况是这样的

开始WA的几百毫秒的都是由于我比较SB造成的,可是跑了10几秒的程序我查了N久也查不出错

最后灵机一动把50000改成60000就过了,也不知道为啥T_T

Description


一个长度为n的序列,有3种操作

1:区间加c

2:区间取为相反数

3:询问区间中选择c个数相乘的所有方案的和mod19940417的值

Solution


这个操作3非常鬼畜,似乎没啥好的办法,但是仔细推导一番会发现这个东西其实是可以区间合并的

用f[i]表示选i个数所有方案和,rt.f[i]=∑ij=0ls.f[j]?rs.f[i?j]

然后注意一点f[0]=1

对于第一种操作加k我们发现对答案f[i]会变为∑ij=0f[j]?Cin?j?ki?j(想一想为什么)

对于第二种操作取反,我们发现只有当c为奇数时答案会取反,这个也很好搞

然后就是打两个标记reverse和add的线段树辣,注意处理标记的顺序

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define ls (rt << 1)
#define rs (rt << 1 | 1)
const int N = 60050, M = 19940417;
int a[N], b[25], c[N][25], add[N << 2], rev[N << 2];
struct node {
    int f[25];
}p[N << 2];
inline int read(int &t) {
    int f = 1;char c;
    while (c = getchar(), c < ‘0‘ || c > ‘9‘) if (c == ‘-‘) f = -1;
    t = c - ‘0‘;
    while (c = getchar(), c >= ‘0‘ && c <= ‘9‘) t = t * 10 + c - ‘0‘;
    t *= f;
}
inline void U(int &x, int y) {
    x += y;
    if (x >= M) x -= M;
    if (x < 0)  x += M;
}
void calc(int rt, int l, int r, int x) {
    for (int i = 1; i <= 20; ++i) {
        int t = 0, tx = 1;
        for (int j = i; j >= 0; --j) {
            U(t, (LL)p[rt].f[j] * c[r - l + 1 - j][i - j] % M * tx % M);
            tx = (LL)tx * x % M;
        }
        b[i] = t;
    }
    for (int i = 1; i <= 20; ++i)   p[rt].f[i] = b[i];
}
void down(int rt, int l, int r) {
    if (rev[rt]) {
        rev[ls] ^= 1, rev[rs] ^= 1;
        for (int i = 1; i <= 20; i += 2)    p[ls].f[i] = (M - p[ls].f[i]) % M;
        for (int i = 1; i <= 20; i += 2)    p[rs].f[i] = (M - p[rs].f[i]) % M;
        add[ls] = (M - add[ls]) % M, add[rs] = (M - add[rs]) % M;
        rev[rt] = 0;
    }
    if (add[rt]) {
        int mid = l + r >> 1;
        U(add[ls], add[rt]), U(add[rs], add[rt]);
        calc(ls, l, mid, add[rt]), calc(rs, mid + 1, r, add[rt]);
        add[rt] = 0;
    }
}
void up(int rt) {
    for (int i = 1; i <= 20; ++i)   p[rt].f[i] = 0;
    for (int i = 0; i <= 20; ++i)
        for (int j = 0; i + j <= 20; ++j) {
            if (i + j == 0) continue;
            U(p[rt].f[i + j], (LL)p[ls].f[i] * p[rs].f[j] % M);
        }
}
void build(int rt, int l, int r) {
    p[rt].f[0] = 1;
    if (l == r) {
        p[rt].f[1] = a[l];
        return;
    }
    int mid = l + r >> 1;
    build(ls, l, mid), build(rs, mid + 1, r);
    up(rt);
}
void reverse(int rt, int l, int r, int L, int R) {
    if (L <= l && R >= r) {
        rev[rt] ^= 1;
        add[rt] = (M - add[rt]) % M;
        for (int i = 1; i <= 20; i += 2) p[rt].f[i] = (M - p[rt].f[i]) % M;
        return;
    }
    down(rt, l, r);
    int mid = l + r >> 1;
    if (L <= mid)   reverse(ls, l, mid, L, R);
    if (R > mid)    reverse(rs, mid + 1, r, L, R);
    up(rt);
}
void inc(int rt, int l, int r, int L, int R, int x) {
    if (L <= l && R >= r) {
        U(add[rt], x);
        calc(rt, l, r, x);
        return;
    }
    int mid = l + r >> 1;
    down(rt, l, r);
    if (L <= mid)   inc(ls, l, mid, L, R, x);
    if (R > mid)    inc(rs, mid + 1, r, L, R, x);
    up(rt);
}
node ask(int rt, int l, int r, int L, int R) {
    if (L <= l && R >= r) {
        return p[rt];
    }
    down(rt, l, r);
    int mid = l + r >> 1;
    node t, t1, t2;
    if (L > mid)    t = ask(rs, mid + 1, r, L, R);
    else if (R <= mid)  t = ask(ls, l, mid, L, R);
    else {
        t.f[0] = 1;
        for (int i = 1; i <= 20; ++i)   t.f[i] = 0;
        t1 = ask(ls, l, mid, L, mid), t2 = ask(rs, mid + 1, r, mid + 1, R);
        for (int i = 0; i <= 20; ++i)
            for (int j = 0; j + i <= 20; ++j) {
                if (i + j == 0) continue;
                U(t.f[i + j], (LL)t1.f[i] * t2.f[j] % M);
            }
    }
    return t;
}
int main() {
    int n, q;
    read(n), read(q);
    for (int i = 0; i <= n; ++i) {
        c[i][0] = c[i][i] = 1;
        for (int j = 1; j < i; ++j) c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % M;
    }
    for (int i = 1; i <= n; ++i)    read(a[i]), a[i] = (a[i] % M + M) % M;
    build(1, 1, n);
    char s[5];
    int x, y, z;
    while (q--) {
        scanf("%s", s);
        if (s[0] == ‘I‘) {
            read(x), read(y), read(z);
            z = (z % M + M) % M;
            inc(1, 1, n, x, y, z);
        }
        else if (s[0] == ‘R‘) {
            read(x), read(y);
            reverse(1, 1, n, x, y);
        }
        else {
            read(x), read(y), read(z);
            z = (z % M + M) % M;
            node t = ask(1, 1, n, x, y);
            printf("%d\n", t.f[z] % M);
        }
    }
    return 0;
}
时间: 2024-10-21 22:17:38

bzoj 2962 序列操作(线段树)的相关文章

bzoj 2962 序列操作——线段树(卷积?)

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2962 如果 _,_,_,-- 变成了 (_+k),(_+k),(_+k),-- ,计算就是在每个括号里选 _ 或 k ,乘起来求和. 为了算那个,枚举选了 j 个 k :剩下那部分的乘积就是sm[cr][ i-j ]!j 和 k 可以在 len 里除了那 i-j 个位置里选,所以乘上 k^j 再乘上 C( len-(i-j) , j ) . 调了2h+竟然只因组合数推导公式写错-- #in

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-1858】序列操作 线段树

1858: [Scoi2010]序列操作 Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 1961  Solved: 991[Submit][Status][Discuss] Description lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成0 1 a b 把[a, b]区间内的所有数全变成1 2 a b 把[a,b]区

bzoj 2962: 序列操作

Description 有一个长度为n的序列,有三个操作1.I a b c表示将[a,b]这一段区间的元素集体增加c,2.R a b表示将[a,b]区间内所有元素变成相反数,3.Q a b c表示询问[a,b]这一段区间中选择c个数相乘的所有方案的和mod 19940417的值. Solution 注意到 \(c\) 比较小,可以直接维护一个 \(O(20^2)\) 的 \(DP\) 即设 \(f[i]\) 表示选了 \(i\) 个数相乘的方案 用线段树维护 合并的话就是 \(f[o][i]=\

bzoj1858: [Scoi2010]序列操作 线段树

线段树,维护七个值两个标记. 用结构体快了一倍…… #include<bits/stdc++.h> #define N (1<<18) #define M (l+r>>1) #define P (k<<1) #define S (k<<1|1) #define K l,r,k #define L l,M,P #define R M+1,r,S #define Z int l=0,int r=n-1,int k=1 using namespace

bzoj1858 [Scoi2010]序列操作——线段树

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1858 线段树...调了一个上午...(后面带 // 的都是改出来的) lazy 标记的下放好麻烦,还得考虑赋值和取反的先后顺序什么的... 因为在取反时把赋值标记 swap 了,所以下放的时候先判断取反再判断赋值... 而且WA了一上午的原因竟然是一开始不慎把取反以为成翻转了,后来没改干净...那个 rev 的名字啊... 总之没有太改变自己最初的想法.改了些细节就A了还是很高兴的! 代码

Bzoj 2752 高速公路 (期望,线段树)

Bzoj 2752 高速公路 (期望,线段树) 题目链接 这道题显然求边,因为题目是一条链,所以直接采用把边编上号.看成序列即可 \(1\)与\(2\)号点的边连得是. 编号为\(1\)的点.查询的时候把\(r - 1\)就好了. 这里的期望显然就是路径的平均值. 期望值: \[\dfrac{\sum_{i=l}^r\sum_{j=l}^{r}dis[i][j]}{C_{r-l+1}^2}\] 下面部分可以直接算出: 上面这一部分比较难维护. 考虑每一条边会被走过多少次. \[ans = \su

ZYB&#39;s Premutation(有逆序数输出原序列,线段树)

ZYB's Premutation Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others) Total Submission(s): 758    Accepted Submission(s): 359 Problem Description ZYB has a premutation P,but he only remeber the reverse log of each pr