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(可持久化数组)
分析
记住怎么建树!
正解
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;
}
这是啥!