【NOI2018模拟3.31】华胥梦天

本文详细介绍了使用线段树解决特定问题的方法,包括维护多个关键属性如和、最大值、次大值等,并通过势能分析确保算法效率。此外,还提供了完整的代码实现。

Description:

这里写图片描述
1<=n<=500000

题解:

这题是吉利营员交流的一个子问题。

线段树维护几个东西:
1.和
2.最大值
3.严格次大值
4.最大值个数
5.min标记

可以用势能分析来分析复杂度。

考虑势能为线段树每个节点所代表区间的不同数的个数和。

一次修改只会增加log个。

对于区间取min,假设取x的min。

x>=max,显然不管。

cmax>x>max,可以直接算答案。

x<<cmax,就需要递归下去。

当x<<cmax时,递归下去势能至少减1。

所以复杂度不会超过O(n log n),常数有点大。

要加读入输出优化。

希望之后搞懂其它的子问题。

Code:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;

const int N = 500005;
const ll INF = 1e18;

int n, m, l, r, x, y, num;
ll ans, px;
int pl, pr;
struct node {
    int mx, cx, ms;
    ll s, bz;
} t[N * 4];

void bin(int i) {
    int u = i + i, v = u + 1;
    if(t[u].mx < t[v].mx) swap(u, v);
    if(t[u].mx == t[v].mx) {
        t[i].mx = t[u].mx; t[i].ms = t[u].ms + t[v].ms;
        t[i].cx = max(t[u].cx, t[v].cx);
    } else {
        t[i].mx = t[u].mx; t[i].ms = t[u].ms;
        t[i].cx = max(t[u].cx, t[v].mx);
    }
    t[i].s = t[u].s + t[v].s; t[i].bz = INF;
}

void dg(int i, int x, int y) {
    if(t[i].bz >= t[i].mx) return;
    if(x == y) {
        t[i].mx = t[i].s = t[i].bz;
        t[i].cx = 0; t[i].ms = 1;
        t[i].bz = INF;
        return;
    }
    if(t[i].bz > t[i].cx) {
        t[i].s = (ll) (t[i].bz - t[i].mx) * t[i].ms + t[i].s;
        t[i].mx = t[i].bz;
        return;
    }
    int m = x + y >> 1;
    t[i + i].bz = min(t[i + i].bz, t[i].bz); dg(i + i, x, m);
    t[i + i + 1].bz = min(t[i + i + 1].bz, t[i].bz); dg(i + i + 1, m + 1, y);
    bin(i);
}

void add(int i, int x, int y) {
    if(y < pl || x > pr) return;
    if(x == y) {
        t[i].bz = INF; t[i].mx = max(0, t[i].mx - px);
        t[i].ms = 1; t[i].s = t[i].mx;
        return;
    }
    int m = x + y >> 1;
    t[i + i].bz = min(t[i + i].bz, t[i].bz); dg(i + i, x, m);
    t[i + i + 1].bz = min(t[i + i + 1].bz, t[i].bz); dg(i + i + 1, m + 1, y);
    add(i + i, x, m); add(i + i + 1, m + 1, y);
    bin(i);
}

void ad(int i, int x, int y) {
    if(y < pl || x > pr) return;
    if(x >= pl && y <= pr) {
        t[i].bz = min(t[i].bz, px); dg(i, x, y);
        return;
    }
    int m = x + y >> 1;
    t[i + i].bz = min(t[i + i].bz, t[i].bz); dg(i + i, x, m);
    t[i + i + 1].bz = min(t[i + i + 1].bz, t[i].bz); dg(i + i + 1, m + 1, y);
    ad(i + i, x, m); ad(i + i + 1, m + 1, y);
    bin(i);
}

void fi(int i, int x, int y) {
    if(y < pl || x > pr) return;
    if(x >= pl && y <= pr) {
        ans += t[i].s; return;
    }
    int m = x + y >> 1; 
    t[i + i].bz = min(t[i + i].bz, t[i].bz); dg(i + i, x, m);
    t[i + i + 1].bz = min(t[i + i + 1].bz, t[i].bz); dg(i + i + 1, m + 1, y);
    fi(i + i, x, m); fi(i + i + 1, m + 1, y);
    bin(i);
}

void read(int &x) {
    char c = ' ';
    for(; c < '0' || c > '9'; c = getchar());
    x = 0; for(; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - 48;
}

void write(int x) {
    if(x == 0) {
        putchar('0'); putchar('\n');
        return;
    }
    int d[20]; d[0] = 0;
    while(x) d[++ d[0]] = x % 10, x /= 10;
    while(d[0]) putchar(d[d[0] --] + 48);
    putchar('\n');
}
int main() {
    freopen("dream.in", "r", stdin);
    freopen("dream.out", "w", stdout);
    scanf("%d %d", &n, &m);
    fo(i, 1, n * 4) t[i].bz = INF;
    fo(i, 1, n) {
        read(x);
        pl = pr = i; px = -x;
        add(1, 1, n);
    }
    fo(i, 1, m) {
        read(num);
        if(num == 1) {
            read(x); read(y);
            x ^= ans; y ^= ans;
            px = y; pl = pr = x;
            add(1, 1, n);
        } else
        if(num == 2) {
            read(l); read(r); read(x);
            l ^= ans; r ^= ans; x ^= ans;
            pl = l, pr = r; px = x;
            ad(1, 1, n);
        } else {
            read(l); read(r);
            l ^= ans; r ^= ans;
            pl = l, pr = r, ans = 0;
            fi(1, 1, n);
            printf("%lld\n", ans);
        }
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值