2025-12-12~14 hetao1733837的刷题笔记

2025-12-12 hetao1733837的刷题笔记

12-12

LG2824 [HEOI2016/TJOI2016] 排序

原题链接:LG2824 [HEOI2016/TJOI2016] 排序

分析

绝世好题!

直接 s o r t sort sort 居然有 80 p t s 80pts 80pts,水成啥了?

考虑怎么加速排序。我们二分最后位置 q q q 的可能值,不妨设为 x x x,至于如何 c h e c k check check,我们把小于 x x x 的位置标记为 0 0 0,否则标记为 1 1 1,那么线段树就有了建树。
01 01 01 序列升序排序显然是把所有 0 0 0 扔到前面,把所有 1 1 1 扔到后面;降序排序显然是把所有 1 1 1 扔到前面,把所有 0 0 0 扔到后面。那不就是两个区间修改拼起来吗?至于 0 , 1 0,1 0,1 的个数,就是区间和!

时间复杂度 O ( n l o g 2 2 n ) O(nlog_{2}^{2}n) O(nlog22n)。

行,搓一下代码!

正解

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, m, a[N];
struct ask{
	int op, l, r;
}inp[N];
int q;
struct segtree{
	int sum[N << 2], tag[N << 2];
	void pushup(int p){
		sum[p] = sum[p << 1] + sum[p << 1 | 1];
	}
	void down(int p, int l, int r){
		if (!tag[p]){
			return ;
		}
		tag[p << 1] = tag[p << 1 | 1] = tag[p];
		int mid = (l + r) >> 1;
		if (tag[p] == 1){
			sum[p << 1] = mid - l + 1;
			sum[p << 1 | 1] = r - mid;
		}
		else{
			sum[p << 1] = sum[p << 1 | 1] = 0;
		}
		tag[p] = 0;
	}
	void build(int p, int l, int r, int x){
		if (l == r){
			if (a[l] >= x){
				sum[p] = 1;
			}
			else{
				sum[p] = 0;
			}
			tag[p] = 0;
			return ;
		}
		int mid = (l + r) >> 1; 
		build(p << 1, l, mid, x);
		build(p << 1 | 1, mid + 1, r, x);
		pushup(p);
		tag[p] = 0;
	}
	void modify(int p, int l, int r, int s, int t, int v){
		if (s > t) 
            return;
		if (s <= l && r <= t){
			sum[p] = v * (r - l + 1);
			if (v)
				tag[p] = 1;
			else
				tag[p] = -1;
			return ;
		}
		down(p, l, r);
		int mid = (l + r) >> 1;
		if (s <= mid)
			modify(p << 1, l, mid, s, min(t, mid), v);
		if (t > mid)
			modify(p << 1 | 1, mid + 1, r, max(s, mid + 1), t, v); 
		pushup(p);
	}
	int query1(int p, int l, int r, int s, int t){
		if (s > t) 
            return 0; 
		if (s <= l && r <= t){
			return sum[p];
		}
		down(p, l, r);
		int mid = (l + r) >> 1;
		int ans = 0;
		if (s <= mid)
			ans += query1(p << 1, l, mid, s, min(t, mid));
		if (t > mid)
			ans += query1(p << 1 | 1, mid + 1, r, max(s, mid + 1), t);
		return ans;
	}
	int query2(int p, int l, int r, int s){
		if (l == r){
			return sum[p];
		}
		down(p, l, r);
		int mid = (l + r) >> 1;
		if (s <= mid)
			return query2(p << 1, l, mid, s);
		else
			return query2(p << 1 | 1, mid + 1, r, s);
	}
}T;
bool check(int x){
	T.build(1, 1, n, x);
	for (int i = 1; i <= m; i++){
		int cnt_1 = T.query1(1, 1, n, inp[i].l, inp[i].r);
		if (inp[i].op == 0){
			if (cnt_1 > 0)
				T.modify(1, 1, n, inp[i].r - cnt_1 + 1, inp[i].r, 1);
			if (inp[i].r - cnt_1 >= inp[i].l) 
				T.modify(1, 1, n, inp[i].l, inp[i].r - cnt_1, 0);
		}
		else{
			if (cnt_1 > 0)
				T.modify(1, 1, n, inp[i].l, inp[i].l + cnt_1 - 1, 1);
			if (inp[i].l + cnt_1 <= inp[i].r)
				T.modify(1, 1, n, inp[i].l + cnt_1, inp[i].r, 0);
		}
	}
	if (T.query2(1, 1, n, q))
		return true;
	return false;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++){
		cin >> a[i];
	}
	for (int i = 1; i <= m; i++){
		cin >> inp[i].op >> inp[i].l >> inp[i].r;
	}
	cin >> q;
	int l = 1, r = n;
	int ans = 1;
	while (l <= r){
		int mid = (l + r) >> 1;
		if (check(mid)){
			ans = mid;
			l = mid + 1;
		}
		else{
			r = mid - 1;
		}
	}
	cout << ans;
	return 0;
}

为啥 RE?

LG4097 【模板】李超线段树 / [HEOI2013] Segment

原题链接:【模板】李超线段树 / [HEOI2013] Segment

分析

没啥吧,但还是理解思想而不回写代码。

正解

cpp 复制代码
#include <bits/stdc++.h>
#define mod1 39989
#define mod2 1000000000
using namespace std;
const int N = 100005;
const double eps = 1e-9;
int n;
int cmp(double x, double y){
	if (x - y > eps)
		return 1;
	if (y - x > eps)
		return -1;
	return 0;
}
struct node{
	double k, b;
}inp[N];
double calc(int id, int x){
	return inp[id].k * x + inp[id].b;
}
int tot;
void add(int xa, int ya, int xb, int yb){
	tot++;
	if (xa == xb){
		inp[tot].k = 0;
		inp[tot].b = max(ya, yb); 
	}
	else{
		inp[tot].k = 1.0 * (yb - ya) / (xb - xa);
		inp[tot].b = ya - inp[tot].k * xa;
	}
}
struct lc_segtree{
	int tr[N << 2];
	lc_segtree(){
		memset(tr, 0, sizeof(tr));
	}
	void modify(int p, int l, int r, int s){
		int &t = tr[p], mid = (l + r) >> 1;
		int tmp_mid = cmp(calc(s, mid), calc(t, mid));
		if (tmp_mid == 1 || (!tmp_mid && s < t))
			swap(s, t);
		int tmp_l = cmp(calc(s, l), calc(t, l));
		int tmp_r = cmp(calc(s, r), calc(t, r));
		if (tmp_l == 1 || (!tmp_l && s < t))
			modify(p << 1, l, mid, s);
		if (tmp_r == 1 || (!tmp_r && s < t))
			modify(p << 1 | 1, mid + 1, r, s);
	}
	void insert(int p, int l, int r, int s, int t, int u){
		if (s <= l && r <= t){
			modify(p, l, r, u);
			return ;
		}
		int mid = (l + r) >> 1;
		if (s <= mid)
			insert(p << 1, l, mid, s, t, u);
		if (t > mid)
			insert(p << 1 | 1, mid + 1, r, s, t, u);
	}
	pair<double, int> pmax(pair<double, int> x, pair<double, int> y){
		if (cmp(x.first, y.first) == -1)
			return y;
		if (cmp(x.first, y.first) == 1)
			return x;
		if (x.second < y.second)
			return x;
		return y;
	}
	pair<double, int> query(int p, int l, int r, int s){
		if (r < s || s < l)
			return {0, 0};
		int mid = (l + r) >> 1;
		double ans = calc(tr[p], s);
		if (l == r)
			return {ans, tr[p]};
		return pmax({ans, tr[p]}, pmax(query(p << 1, l, mid, s), query(p << 1 | 1, mid + 1, r, s)));
	}
}T;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	int lst_ans = 0;
	for (int cs = 1; cs <= n; cs++){
		int op;
		cin >> op;
		if (op == 1){
			int xa, xb, ya, yb;
			cin >> xa >> ya >> xb >> yb;
			xa = (xa + lst_ans - 1 + mod1) % mod1 + 1;
			xb = (xb + lst_ans - 1 + mod1) % mod1 + 1;
			ya = (ya + lst_ans - 1 + mod2) % mod2 + 1;
			yb = (yb + lst_ans - 1 + mod2) % mod2 + 1;
			if (xa > xb){
				swap(xa, xb);
				swap(ya, yb);
			}
			add(xa, ya, xb, yb);
			T.insert(1, 1, mod1, xa, xb, tot);
		}
		else{
			int x;
			cin >> x;
			x = (x + lst_ans - 1 + mod1) % mod1 + 1;
			lst_ans = T.query(1, 1, mod1, x).second;
			cout << lst_ans << '\n';
		}
	}
}

LG4577 [FJOI2018] 领导集团问题

原题链接:[FJOI2018] 领导集团问题

初步思路

ber,这个输入给的是啥啊......

行,也是理解了。XX,我XX还是理解不了啊/(ㄒoㄒ)/~~

看题解!彻底怒了!

行,点集,我是XX。

那样例我理解了,开始思考,到八点想不出来看题解。线段树合并?

对于每个节点,若其儿子不小于他,就可以入选。特判叶子节点,如果其不小于其父亲,一样可以入选。我们好像翻译了一遍题目。

不是哥们,那我最坏 O ( n 2 ) O(n^2) O(n2) 要是数据没有菊花图之类的东西,岂不是赢了?

那我为何不写一个暴力验证题有没有读懂?坏了,没读懂,我找AI救一下。

确实错了,晓伟我错了,我确实应该补补语文了/(ㄒoㄒ)/~~

正确思路

显然,组成的点集构成了最长不降子序列。

形式化的,即求 ∣ S m a x ∣ |S_{max}| ∣Smax∣ 使得 ∀ i , j ( a n c e s t o r o f i ) ∈ S , w i ≤ w j \forall i,j(ancestor\ of\ i)\in S,w_i\le w_j ∀i,j(ancestor of i)∈S,wi≤wj。

那就是 D P DP DP。

这题解咋这么意识流啊?

我决定放弃树上启发式合并,看线段树合并的题解。

21点02分

初识正解。

考虑 LIS 的思路,分讨原节点是否选择。

设 d p u , i dp_{u,i} dpu,i 表示 u u u 子树中点权最小值取 i i i 的最大答案。

不选 u u u 节点时,与别的子树 v v v合并。转移如下:
f n e w ( u , i ) = max ⁡ { f ( u , i ) + max ⁡ j ≥ i f ( v , j ) , f ( v , i ) + max ⁡ j ≥ i f ( u , j ) } f_{new}(u,i)=\max\{f(u,i)+\max\limits_{j\ge i}f(v,j),f(v,i)+\max\limits_{j\ge i}f(u,j)\} fnew(u,i)=max{f(u,i)+j≥imaxf(v,j),f(v,i)+j≥imaxf(u,j)}

选择 u u u,转移如下:
f ( u , w u ) ← m a x 1 + ∑ v max ⁡ j ≥ w u f ( v , j ) f(u, w_u)\leftarrow {max} 1+\sum\limits{v}\max\limits_{j\ge w_u}f(v,j) f(u,wu)←max1+v∑j≥wumaxf(v,j)

那就上线段树合并,将不存在的设为 0 0 0,选 u u u 就是单点修改,线段树合并的时候需要再维护一个后缀最大值。

我在理解不了啊/ll

正解

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
const int M = N * 32;
int n, w[N], rk[N], id[N];
vector<int> e[N];
bool cmp(int x, int y){
    return w[x] < w[y];
}
struct segtree{
    int tr[M], ls[M], rs[M], mx[M], tag[M], cnt[M], tot;
    void pushup(int p){
        mx[p] = max(mx[ls[p]], mx[rs[p]]);
        cnt[p] = cnt[ls[p]] + cnt[rs[p]];
    }
    void pushdown(int p, int v){
        if (mx[p]){
            mx[p] += v;
            tag[p] += v;
        }
    }
    void down(int p){
        if (tag[p]){
            pushdown(ls[p], tag[p]);
            pushdown(rs[p], tag[p]);
            tag[p] = 0;
        }
    }
    void insert(int &p, int l, int r, int s, int v){
        if (!p)
            p = ++tot;
        if (l == r){
            if (!mx[p])
                cnt[p] = 1;
            mx[p] = max(mx[p], v);
        } 
        else{
            down(p);
            int mid = (l + r) >> 1;
            if (s <= mid){
                insert(ls[p], l, mid, s, v);
            }
            else{
                insert(rs[p], mid + 1, r, s, v);
            }
            pushup(p);
        }
    }
    void modify(int &p, int l, int r, int s, int t, int v){
        if (!p)
            p = ++tot;
        if (l == s && r == t)
            pushdown(p, v);
        else{
            down(p);
            int mid = (l + r) >> 1;
            if (t <= mid)
                modify(ls[p], l, mid, s, t, v);
            else if (s > mid)
                modify(rs[p], mid + 1, r, s, t, v);
            else{
                modify(ls[p], l, mid, s, mid, v);
                modify(rs[p], mid + 1, r, mid + 1, t, v);
            }
            pushup(p);
        }
    }
    int query(int &p, int l, int r, int s, int t){
        if (!p)
            return 0;
        if (l == s && r == t)
            return mx[p];
        else{
            down(p);
            int mid = (l + r) >> 1;
            if (t <= mid)
                return query(ls[p], l, mid, s, t);
            else if (s > mid)
                return query(rs[p], mid + 1, r, s, t);
            else
                return max(query(ls[p], l, mid, s, mid), query(rs[p], mid + 1, r, mid + 1, t));
        }
    }
    void merge(int &p, int l, int r, int pre, int mx1, int mx2){
        if (!p){
            p = pre;
            pushdown(p, mx1);
        }
        else if (!pre){
            pushdown(p, mx2);
        }
        else if (l == r){
            int val = max({mx[p] + mx[pre], mx[p] + mx2, mx[pre] + mx1});
            if (val && !mx[p])
                cnt[p] = 1;
            mx[p] = val;
        }
        else{
            down(p);
            down(pre);
            int mid = (l + r) >> 1;
            int rp_mx = mx[rs[p]];
            int rpre_mx = mx[rs[pre]];
            merge(ls[p], l, mid, ls[pre], max(mx1, rp_mx), max(mx2, rpre_mx));
            merge(rs[p], mid + 1, r, rs[pre], mx1, mx2);
            pushup(p);
        }
    }
}T;
void dfs(int u, int fa){
    int ans = 1;
    for (auto v : e[u]){
        if (v != fa){
            dfs(v, u);
            ans += T.query(T.tr[v], 1, n, rk[u], n);
        }
    }
    bool flag = 1;
    for (auto v : e[u]){
        if (v != fa){
            if (flag){
                T.tr[u] = T.tr[v];
                flag = 0;
            }
            else{
                T.merge(T.tr[u], 1, n, T.tr[v], 0, 0);
            }
        }
    }
    T.insert(T.tr[u], 1, n, rk[u], ans);
}
int res;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i++){
        cin >> w[i];
        id[i] = i;
    }
    sort(id + 1, id + n + 1, cmp);
    for (int i = 1; i <= n; i++){
        rk[id[i]] = rk[id[i - 1]] + (w[id[i - 1]] != w[id[i]]);
    }
    for (int i = 2; i <= n; i++){
        int v;
        cin >> v;
        e[v].push_back(i);
    }
    dfs(1, 0);
    cout << T.query(T.tr[1], 1, n, 1, n);
}

12-13

LG3919 【模板】可持久化线段树 1(可持久化数组)

原题链接:【模板】可持久化线段树 1(可持久化数组)

分析

记住怎么建树!

正解

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1000005;
int n, tot, tr[N * 32], ls[N * 32], rs[N * 32];
int m, a[N], rt[N];
struct p_segtree {
    void pushup(int p, int l, int r){
        if (l == r)
            return;
        tr[p] = tr[ls[p]] + tr[rs[p]];
    }
    void build(int &p, int l, int r){
        p = ++tot;
        if (l == r){
            tr[p] = a[l];
            return;
        }
        int mid = (l + r) >> 1;
        build(ls[p], l, mid);
        build(rs[p], mid + 1, r);
        pushup(p, l, r);
    }
    void modify(int &p, int pre, int l, int r, int pos, int val){
        p = ++tot;
        ls[p] = ls[pre];
        rs[p] = rs[pre];
        if (l == r){
            tr[p] = val;
            return ;
        }
        int mid = (l + r) >> 1;
        if (pos <= mid)
            modify(ls[p], ls[pre], l, mid, pos, val);
        else
            modify(rs[p], rs[pre], mid + 1, r, pos, val);
        pushup(p, l, r);
    }
    int query(int p, int l, int r, int pos){
        if (l == r)
            return tr[p];
        int mid = (l + r) >> 1;
        if (pos <= mid)
            return query(ls[p], l, mid, pos);
        else
            return query(rs[p], mid + 1, r, pos);
    }
}T;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++){
        cin >> a[i];
    }
    T.build(rt[0], 1, n);
    for (int cs = 1; cs <= m; cs++){
        int v, op, p, c;
        cin >> v >> op >> p;
        if (op == 1){
            cin >> c;
            T.modify(rt[cs], rt[v], 1, n, p, c);
        } 
        else{
            cout << T.query(rt[v], 1, n, p) << '\n';
            rt[cs] = rt[v]; 
        }
    }
}

LG3834 【模板】可持久化线段树 2

原题链接:【模板】可持久化线段树 2

分析

q u e r y query query 的是下标!

正解

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
int n, m, tot;
int a[N], b[N], rt[N];
int tr[N * 20], ls[N * 20], rs[N * 20];
struct persistent_segtree{
    void build(int &p, int l, int r){
        p = ++tot;
        tr[p] = 0;
        if (l == r) 
            return;
        int mid = (l + r) >> 1;
        build(ls[p], l, mid);
        build(rs[p], mid + 1, r);
    }
    void modify(int &p, int pre, int l, int r, int pos){
        p = ++tot;
        ls[p] = ls[pre];
        rs[p] = rs[pre];
        tr[p] = tr[pre] + 1;
        if (l == r) 
            return;
        int mid = (l + r) >> 1;
        if (pos <= mid)
            modify(ls[p], ls[pre], l, mid, pos);
        else
            modify(rs[p], rs[pre], mid + 1, r, pos);
    }
    int query(int p, int pre, int l, int r, int k){
        if (l == r) 
            return l;
        int mid = (l + r) >> 1;
        int tmp = tr[ls[p]] - tr[ls[pre]];
        if (k <= tmp)
            return query(ls[p], ls[pre], l, mid, k);
        else
            return query(rs[p], rs[pre], mid + 1, r, k - tmp);
    }
} T;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++){
        cin >> a[i];
        b[i] = a[i];
    }
    sort(b + 1, b + n + 1);
    int len = unique(b + 1, b + n + 1) - b - 1;
    T.build(rt[0], 1, len);
    for (int i = 1; i <= n; i++){
        int pos = lower_bound(b + 1, b + len + 1, a[i]) - b;
        T.modify(rt[i], rt[i - 1], 1, len, pos);
    }
    while (m--){
        int l, r, k;
        cin >> l >> r >> k;
        int tmp = T.query(rt[r], rt[l - 1], 1, len, k);
        cout << b[tmp] << '\n';
    }
}

LG3332 [ZJOI2013] K大数查询

原题链接:[ZJOI2013] K大数查询

分析

别搞哥们,树套树?我觉得学一下是正确的。

权值线段树套区间线段树+动态开点?吃大了,ZR 真是啥都往题单里扔。

行,我觉得 he 是对的。

正解

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 50005;
int n, m, tot;
struct q{
	int op, l, r, c;
}inp[N];
int ls[N * 400], rs[N * 400]; 
int b[N];
int rt[N << 2], tag[N * 400], sz[N * 400]; 
struct segtree{
	void pushup(int p){
		sz[p] = sz[ls[p]] + sz[rs[p]];
	}
	void down(int p, int l, int r){
		int &v = tag[p];
		int mid = (l + r) >> 1;
		if (!ls[p])
			ls[p] = ++tot;
		if (!rs[p])
			rs[p] = ++tot;
		tag[ls[p]] += v;
		tag[rs[p]] += v;
		sz[ls[p]] += v * (mid - l + 1);
		sz[rs[p]] += v * (r - mid); 
		v = 0;
	}
	void modify(int &p, int l, int r, int s, int t){
		if (!p)
			p = ++tot;
		if (s <= l && r <= t){
			++tag[p];
			sz[p] += r - l + 1;
			return ;
		}
		if (tag[p])
			down(p, l, r);
		int mid = (l + r) >> 1;
		if (s <= mid)
			modify(ls[p], l, mid, s, t);
		if (t > mid)
			modify(rs[p], mid + 1, r, s, t);
		pushup(p);
	}
	int query(int &p, int l, int r, int s, int t){
		if (!p)
			return 0;
		if (s <= l && r <= t)
			return sz[p];
		if (tag[p])
			down(p, l, r);
		int mid = (l + r) >> 1;
		int res = 0;
		if (s <= mid)
			res += query(ls[p], l, mid, s, t);
		if (t > mid)
			res += query(rs[p], mid + 1, r, s, t);
		return res;
	}
}T;
void add(int p, int l, int r, int s, int t, int k){
	T.modify(rt[p], 1, n, s, t);
	if (l == r)
		return ;
	int mid = (l + r) >> 1;
	if (k <= mid)
		add(p << 1, l, mid, s, t, k);
	else
		add(p << 1 | 1, mid + 1, r, s, t, k);
}
int ask(int p, int l, int r, int s, int t, int k){
	if (l == r)
		return b[l];
	int mid = (l + r) >> 1;
	int tmp = T.query(rt[p << 1 | 1], 1, n, s, t);
	if (tmp < k)
		return ask(p << 1, l, mid, s, t, k - tmp);
	else
		return ask(p << 1 | 1, mid + 1, r, s, t, k);
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	int len = 0;
	for (int i = 1; i <= m; i++){
		cin >> inp[i].op >> inp[i].l >> inp[i].r >> inp[i].c;
		if (inp[i].op == 1)
			b[++len] = inp[i].c;
	}
	sort(b + 1, b + len + 1);
	len = unique(b + 1, b + len + 1) - b - 1;
	for (int i = 1; i <= m; i++){
		if (inp[i].op == 1)
			inp[i].c = lower_bound(b + 1, b + len + 1, inp[i].c) - b;
	}
	for (int i = 1; i <= m; i++){
		if (inp[i].op == 1){
			add(1, 1, len, inp[i].l, inp[i].r, inp[i].c);
		}
		else{
			cout << ask(1, 1, len, inp[i].l, inp[i].r, inp[i].c) << '\n';
		}
	}
}

这玩意叫动态开点?我不信。

不是哥们,真得学离线算法了。

12-14

LG14397/LOJ2732 [JOISC 2016] 雇佣计划 / Employment

原题链接1:[JOISC 2016]Employment

原题链接2:「JOISC 2016 Day 2」雇佣计划

分析

这是一个图论建模的奇妙解法,来自aoao。

简化题目,给定一个序列,有两种操作,单点修改和查询。对于每次查询操作,给定一个值 k,将大于等于 k 的位置赋值为 1,其余赋值为 0,求 1 的连通块数量。

那么,把相邻且都为 1 的点连边,变成了给定点数和边数,求连通块个数。

很简单,连通块个数 = 点数 - 边数。

那么,树状数组维护一个桶即可。

正解

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 600005;
int n, m, top, a[N], b[N];
struct BIT{
	int c[N];
	void add(int x, int v){
		for (int i = x; i <= top; i += i & (-i))
			c[i] += v;
	}
	int query(int x){
		int res = 0;
		for (int i = x; i; i -= i & (-i))
			res += c[i];
		return res;
	}
}tr1, tr2;
struct ask{
	int op, a, b;
}inp[N];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++){
		cin >> a[i];
		b[++top] = a[i];
	}
	for (int i = 1; i <= m; i++){
		cin >> inp[i].op;
		if (inp[i].op == 1){
			cin >> inp[i].a;
			b[++top] = inp[i].a;
		}
		else{
			cin >> inp[i].a >> inp[i].b;
			b[++top] = inp[i].b;
		}
	}
	sort(b + 1, b + top + 1);
	top = unique(b + 1, b + top + 1) - b - 1;
	for (int i = 1; i <= n; i++){
		a[i] = lower_bound(b + 1, b + top + 1, a[i]) - b;
		tr2.add(a[i], 1);
		if (i > 1)
			tr1.add(min(a[i], a[i - 1]), 1);	
	}
	for (int i = 1; i <= m; i++){
		if (inp[i].op == 1){
			inp[i].a = lower_bound(b + 1, b + top + 1, inp[i].a) - b;
			cout << (tr2.query(top) - tr2.query(inp[i].a - 1)) - (tr1.query(top) - tr1.query(inp[i].a - 1)) << '\n';
		}
		else{
			inp[i].b = lower_bound(b + 1, b + top + 1, inp[i].b) - b;
			int pos = inp[i].a;
			tr2.add(a[pos], -1);
			if (pos > 1){
				tr1.add(min(a[pos], a[pos - 1]), -1);
			}
			if (pos < n){
				tr1.add(min(a[pos], a[pos + 1]), -1);
			}
			a[pos] = inp[i].b;
			tr2.add(a[pos], 1);
			if (pos > 1){
				tr1.add(min(a[pos], a[pos - 1]), 1);
			}
			if (pos < n){
				tr1.add(min(a[pos], a[pos + 1]), 1);
			}
		}
	}
}

LG5354 [Ynoi Easy Round 2017] 由乃的 OJ

原题链接:[Ynoi Easy Round 2017] 由乃的 OJ

分析

为了写这题,我们还是先写这题吧。我对这题已有初步思路,尝试自己写一下。贪心部分没有自己写出来,不过思想已经了解,代码如下:

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 100005;
int n, m;
pair<string, int> ops[N];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++){
        cin >> ops[i].first >> ops[i].second;
    }
    int ans = 0, val = 0;
    for (int i = 30; i >= 0; i--){
        int res0 = 0; 
        int res1 = 1;
        for (auto op : ops){
            int t = (op.second >> i) & 1;
            if (op.first == "AND"){
                res0 &= t;
                res1 &= t;
            } 
            else if (op.first == "OR"){
                res0 |= t;
                res1 |= t;
            }
            else{ 
                res0 ^= t;
                res1 ^= t;
            }
        }
        if (val + (1 << i) <= m){
            if (res1 > res0){
                val += (1 << i);
                ans += (res1 << i);
            }
            else{
                ans += (res0 << i);
            }
        } 
        else{ 
            ans += (res0 << i);
        }
    }
    ans = val;
    for (auto op : ops){
        if (op.first == "AND"){
            ans &= op.second;
        } 
        else if (op.first == "OR"){
            ans |= op.second;
        } 
        else{
            ans ^= op.second;
        }
    }
    cout << ans;
}

显然,本题只是这题的树上待修版本。

经过这一题的启发,我们似乎只需要把路径变成区间即可。那就上树剖?复杂度不太对吧。

那咋办?

正确思路

感觉类似状压。如果用这题的贪心思路,是会 TLE 的,硬来线段树,需要考虑如何合并区间。

我们设 f 0 l , i f0_{l,i} f0l,i 表示左子区间第 i i i 为是 0 0 0 时的值, f 1 l , i f1_{l,i} f1l,i 同理。

则有
f 0 u = ( f 0 l & f 1 r ) ∣ ( ( ∼ f 0 l ) & f 1 r ) f0_{u}=(f0_{l} \& f1_r) | ((\sim f0_l) \& f1_r) f0u=(f0l&f1r)∣((∼f0l)&f1r) f 1 u = ( f 1 l & f 1 r ) ∣ ( ( ∼ f 1 l ) & f 0 r ) f1_{u}=(f1_{l} \& f1_r) | ((\sim f1_l) \& f0_r) f1u=(f1l&f1r)∣((∼f1l)&f0r)

按之前贪心即可。

正解

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const int N = 200010;
const ll LIM = (ll)-1; 
int n, Q, K;
int first[N], cnte, cntn;
int dep[N], son[N], siz[N], top[N], pos[N], back[N], fa[N];
int op[N];
ll val[N];
struct data {
    ll f0, f1, inv0, inv1;
    data() { f0 = f1 = inv0 = inv1 = 0; }
} a[N << 2];
struct edge {
    int to, next;
} e[N << 1];
inline void add(int u, int v) {
    e[++cnte] = (edge){v, first[u]};
    first[u] = cnte;
    e[++cnte] = (edge){u, first[v]};
    first[v] = cnte;
}
void dfs1(int u, int f) {
    dep[u] = dep[f] + 1;
    siz[u] = 1;
    son[u] = 0;
    fa[u] = f;
    for (int i = first[u]; ~i; i = e[i].next) {
        int v = e[i].to;
        if (v == f) continue;
        dfs1(v, u);
        siz[u] += siz[v];
        if (siz[v] > siz[son[u]]) son[u] = v;
    }
}
void dfs2(int u, int t) {
    top[u] = t;
    pos[u] = ++cntn;
    back[cntn] = u;
    if (son[u]) dfs2(son[u], t);
    for (int i = first[u]; ~i; i = e[i].next) {
        int v = e[i].to;
        if (pos[v]) continue;
        dfs2(v, v);
    }
}
inline ll calc(ll num, int x) {
    if (op[x] == 1) return num & val[x];
    if (op[x] == 2) return num | val[x];
    if (op[x] == 3) return num ^ val[x];
    return 0;
}
data update(data l, data r) {
    data re;
    re.f0 = ((l.f0 & r.f1) | ((~l.f0) & r.f0));
    re.f1 = ((l.f1 & r.f1) | ((~l.f1) & r.f0));
    re.inv0 = ((r.inv0 & l.inv1) | ((~r.inv0) & l.inv0));
    re.inv1 = ((r.inv1 & l.inv1) | ((~r.inv1) & l.inv0));
    return re;
}
void build(int l, int r, int num) {
    if (l == r) {
        a[num].f0 = a[num].inv0 = calc(0, back[l]);
        a[num].f1 = a[num].inv1 = calc(LIM, back[l]);
        return;
    }
    int mid = (l + r) >> 1;
    build(l, mid, num << 1);
    build(mid + 1, r, (num << 1) + 1);
    a[num] = update(a[num << 1], a[(num << 1) + 1]);
}
void change(int l, int r, int num, int pos) {
    if (l == r) {
        a[num].f0 = a[num].inv0 = calc(0, back[l]);
        a[num].f1 = a[num].inv1 = calc(LIM, back[l]);
        return;
    }
    int mid = (l + r) >> 1;
    if (mid >= pos)
        change(l, mid, num << 1, pos);
    else
        change(mid + 1, r, (num << 1) + 1, pos);
    a[num] = update(a[num << 1], a[(num << 1) + 1]);
}
data query(int l, int r, int ql, int qr, int num) {
    if (l == ql && r == qr) return a[num];
    int mid = (l + r) >> 1;
    if (mid >= qr)
        return query(l, mid, ql, qr, num << 1);
    else {
        if (mid < ql)
            return query(mid + 1, r, ql, qr, (num << 1) + 1);
        else
            return update(query(l, mid, ql, mid, num << 1),
                          query(mid + 1, r, mid + 1, qr, (num << 1) + 1));
    }
}
data ans1[N], ans2[N];
int cnt1, cnt2;
data solve(int x, int y) {
    cnt1 = cnt2 = 0;
    while (top[x] != top[y]) {
        if (dep[top[x]] >= dep[top[y]]) {
            ans1[++cnt1] = query(1, n, pos[top[x]], pos[x], 1);
            x = fa[top[x]];
        } else {
            ans2[++cnt2] = query(1, n, pos[top[y]], pos[y], 1);
            y = fa[top[y]];
        }
    }
    if (dep[x] > dep[y])
        ans1[++cnt1] = query(1, n, pos[y], pos[x], 1);
    else
        ans2[++cnt2] = query(1, n, pos[x], pos[y], 1);
    data re;
    for (int i = 1; i <= cnt1; i++)
        swap(ans1[i].f0, ans1[i].inv0), swap(ans1[i].f1, ans1[i].inv1);
    if (cnt1) {
        re = ans1[1];
        for (int i = 2; i <= cnt1; i++) re = update(re, ans1[i]);
        if (cnt2) re = update(re, ans2[cnt2]);
    } else {
        re = ans2[cnt2];
    }
    for (int i = cnt2 - 1; i >= 1; i--) re = update(re, ans2[i]);
    return re;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    memset(first, -1, sizeof(first));
    cin >> n >> Q >> K;
    for (int i = 1; i <= n; i++) {
        cin >> op[i] >> val[i];
    }
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        add(u, v);
    }
    dfs1(1, 0);
    dfs2(1, 1);
    build(1, n, 1);
    while (Q--) {
        int opt, t1, t2;
        ll t3;
        cin >> opt >> t1 >> t2 >> t3;
        if (opt == 2) {
            op[t1] = t2;
            val[t1] = t3;
            change(1, n, 1, pos[t1]);
        } else {
            data re = solve(t1, t2);
            ll ans = 0;
            for (int i = 63; i >= 0; i--) {
                ll tmp0 = (re.f0 >> i) & 1ull;
                ll tmp1 = (re.f1 >> i) & 1ull;
                if (tmp0 >= tmp1 || (1ull << i) > t3) {
                    if (tmp0) ans |= (1ull << i);
                } else {
                    if (tmp1) ans |= (1ull << i);
                    t3 -= (1ull << i);
                }
            }
            cout << ans << '\n';
        }
    }
    return 0;
}

这是啥!

相关推荐
逑之28 分钟前
C语言笔记10:sizeof和strlen,指针与数组
c语言·笔记·算法
saoys31 分钟前
Opencv 学习笔记:创建与原图等尺寸的空白图像
笔记·opencv·学习
求梦82032 分钟前
【力扣hot100题】旋转图像(15)
算法·leetcode·职场和发展
C雨后彩虹5 小时前
任务最优调度
java·数据结构·算法·华为·面试
晓幂6 小时前
【2025】HECTF
笔记·学习·web安全
少林码僧7 小时前
2.31 机器学习神器项目实战:如何在真实项目中应用XGBoost等算法
人工智能·python·算法·机器学习·ai·数据挖掘
钱彬 (Qian Bin)7 小时前
项目实践15—全球证件智能识别系统(切换为Qwen3-VL-8B-Instruct图文多模态大模型)
人工智能·算法·机器学习·多模态·全球证件识别
微露清风7 小时前
系统性学习C++-第十八讲-封装红黑树实现myset与mymap
java·c++·学习
Niuguangshuo7 小时前
EM算法详解:解密“鸡生蛋“的机器学习困局
算法·机器学习·概率论
a3158238068 小时前
Android 大图显示策略优化显示(一)
android·算法·图片加载·大图片