CF杂题选讲

杂题选讲

G. Periodic RMQ Problem

题意

​ 需要对区间进行两种操作:1、将 [ l , r ] [l,r] [l,r]区间所有 a i a_{i} ai赋值为 x x x,2、求区间 [ l , r ] [l,r] [l,r]内最小值

分析

​ 观察到数据范围可知,区间长度为 n ∗ k n*k nk,高达 1 e 9 1e9 1e9,所以不能用普通的线段树进行维护,区间之间重复度很高并且需要支持的操作数只有 1 e 5 1e5 1e5,考虑动态开点线段树。 动态开点线段树不需要提前建树,只需要在操作的时候去访问节点,如果一个点都没有查询到,说明这个区间是没有进行修改的,然后一边修改一边建立新的儿子节点即可,然而对于记录区间最小值,显然不可以暴力的查询最小值,需要提前用 S T ST ST表储存 R M Q RMQ RMQ,可能会存在区间横跨多个数段的情况, S T ST ST表应该存储两个区间段,若横跨了多个区间也能快速得到区间最小值

代码

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int maxn = 2e5 + 10;
const int maxm = 1e7 + 10;
const int logn = 20;
 
int n, m, q, a[maxn], lg[maxn], f[maxn][logn];
int tag[maxm], tree[maxm], ls[maxm], rs[maxm], tot, rt;
 
int qmin(int l, int r) {
	if (l > r) {
		return 2e9;
	}
	if (r - l + 1 >= n) {
		return f[1][lg[n * 2]];
	}
	int tmp = (l - 1) / n;
	l -= tmp * n;
	r -= tmp * n;
	int k = lg[r - l + 1];
	return min(f[l][k], f[r - (1 << k) + 1][k]);
}
 
void pushdown(int x) {
	if (tag[x] == -1) {
		return;
	}
	if (!ls[x]) {
		ls[x] = ++tot;
	}
	if (!rs[x]) {
		rs[x] = ++tot;
	}
	tag[ls[x]] = tag[rs[x]] = tree[ls[x]] = tree[rs[x]] = tag[x];
	tag[x] = -1;
}
 
void pushup(int x, int l, int r) {
	int mid = (l + r) >> 1;
	tree[x] = min(ls[x] ? tree[ls[x]] : qmin(l, mid), rs[x] ? tree[rs[x]] : qmin(mid + 1, r));
}
 
void update(int &rt, int l, int r, int ql, int qr, int x) {
	if (!rt) {
		rt = ++tot;
	}
	if (ql <= l && r <= qr) {
		tree[rt] = tag[rt] = x;
		return;
	}
	pushdown(rt);
	int mid = (l + r) >> 1;
	if (ql <= mid) {
		update(ls[rt], l, mid, ql, qr, x);
	}
	if (qr > mid) {
		update(rs[rt], mid + 1, r, ql, qr, x);
	}
	pushup(rt, l, r);
}
 
int query(int rt, int l, int r, int ql, int qr) {
	if (!rt) {
		return qmin(max(l, ql), min(r, qr));
	}
	if (ql <= l && r <= qr) {
		return tree[rt];
	}
	pushdown(rt);
	int mid = (l + r) >> 1, res = 2e9;
	if (ql <= mid) {
		res = min(res, query(ls[rt], l, mid, ql, qr));
	}
	if (qr > mid) {
		res = min(res, query(rs[rt], mid + 1, r, ql, qr));
	}
	return res;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    memset(tag, -1, sizeof(tag));
	lg[0] = -1;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		a[i + n] = a[i];
	}
	for (int i = 1; i <= n * 2; ++i) {
		f[i][0] = a[i];
		lg[i] = lg[i >> 1] + 1;
	}
	for (int j = 1; (1 << j) <= n * 2; ++j) {
		for (int i = 1; i + (1 << j) - 1 <= n * 2; ++i) {
			f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
		}
	}
	cin >> q;
	while (q--) {
		int op, l, r, x;
		cin >> op >> l >> r;
		if (op == 1) {
			cin >> x;
			update(rt, 1, n * m, l, r, x);
		} else {
            cout << query(rt, 1, n * m, l, r) << '\n';
		}
	}
	return 0;
}

D. Imbalanced Array

题意

​ 求数组中每个子段的最大值与最小值的差的和

分析

​ 因为子段一定是连续的,考虑 a i a_{i} ai什么时候是最大值或最小值,因为只有当 a i a_{i} ai是最大值或最小值的时候才对答案有贡献。

​ 当 a i a_{i} ai是最大值的时候,一定是 a i − x a_{i}-x aix,那么它对于答案是正向作用,当 a i a_{i} ai是最小值的时候,一定是 x − a i x-a_{i} xai,那么它对于答案是负向作用。因此要找到 i i i左右两边比 a i a_{i} ai大的数的下标,比 a i a_{i} ai小的数的下标,考虑单调栈,然后就是计算贡献了。

代码

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
LL f(vector<LL> a) {
    int n = a.size() - 1;
    stack<int> st;
    vector<int> l(n + 1), r(n + 1);
    for (int i = 1; i <= n; i++) {
        while (!st.empty() && a[st.top()] < a[i]) {
            st.pop();
        }
        if (st.empty()) {
            l[i] = 0;
        } else {
            l[i] = st.top();
        }
        st.push(i);
    }
    while (!st.empty()) {
        st.pop();
    }
    for (int i = n; i >= 1; i--) {
        while (!st.empty() && a[st.top()] <= a[i]) {
            st.pop();
        }
        if (st.empty()) {
            r[i] = n + 1;
        } else {
            r[i] = st.top();
        }
        st.push(i);
    }
    LL res = 0;
    for (int i = 1; i <= n; i++) {
        res += 1LL * a[i] * (i - l[i]) * (r[i] - i);
    }
    return res;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<LL> a(n + 1);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    LL ans = 0;
    ans += f(a);
    for (int i = 1; i <= n; i++) {
        a[i] = -a[i];
    }
    ans += f(a);
    cout << ans << '\n';
    return 0;
}

F. MEX Queries

题意

​ 给定一个空集合,需要完成三种操作:1、将 [ l , r ] [l,r] [l,r]中所有不存在的数加进去,2、将 [ l , r ] [l,r] [l,r]中所有存在的数删掉,3、将 [ l , r ] [l,r] [l,r]中所有不存在的数加进去并且将所有存在的数删去

分析

​ 根据题意,可以使用类似于珂朵莉树的数据结构,随机数据下时间复杂度为亚线性。对于一个区间,当一个点的值为0时代表这个点已经被删除或者未加入,当值为1时,表示它就在集合里。对于一个区间,只需要记录 l l l点和 r + 1 r+1 r+1点的状态,因为操作 1 1 1和操作 2 2 2相当于把 [ l , r ] [l,r] [l,r]全部设置为 1 1 1,把 [ l , r ] [l,r] [l,r]全部设置为 0 0 0 ( l , r + 1 ) (l,r+1) (l,r+1)之间的状态已经不再需要了,这样每次需要处理的次数就非常少,复杂度接近线性

代码

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
map<LL, LL> s;
void f(LL x) {
    auto it = prev(s.upper_bound(x));
    s[x] = it->second;
}
void cover(LL l, LL r, int x) {
    f(l);
    f(r + 1);
    auto it = s.find(l);
    while (it->first != r + 1) {
        it = s.erase(it);
    }
    s[l] = x;
}
void rev(LL l, LL r) {
    f(l);
    f(r + 1);
    auto it = s.find(l);
    while (it->first != r + 1) {
        it->second ^= 1;
        it++;
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int q;
    cin >> q;
    s[1] = 0;
    while (q--) {
        int op;
        LL l, r;
        cin >> op >> l >> r;
        if (op == 1) {
            cover(l, r, 1);
        } else if (op == 2) {
            cover(l, r, 0);
        } else {
            rev(l, r);
        }
        auto it = s.begin();
        while (it->second) {
            it++;
        }
        cout << it->first << '\n';
    }
    return 0;
}

E. Chemistry in Berland

题意

​ 有 n n n种材料,现在 n n n种材料每种有 b i b_{i} bi个,一共需要 a i a_{i} ai个,然后存在一种转换: k i k_{i} ki x i x_{i} xi材料可以合成1个 i i i材料,并且1个 i i i材料也可以合成1个 x i x_{i} xi材料,问能否满足全部需求

分析

​ 通过数据范围可知,材料之间的转化过程就是一棵树形结构,那么可以定义节点 1 1 1作为根节点 d f s dfs dfs,倘若现在的材料还有剩余,则把多的都等比例扔给其父节点,若不够,则把需求按比例扔给其父节点,最后判断根节点是否大于等于 0 0 0即可

代码

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int n;
LL a[100010], b[100010];
double f[100010];
vector<pair<LL, LL> > G[100010];
void dfs(int u) {
    f[u] = b[u] - a[u];
    for (auto v : G[u]) {
        dfs(v.first);
        if (f[v.first] >= 0) {
            f[u] += f[v.first];
        } else {
            f[u] += f[v.first] * v.second;
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> b[i];
    }
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = 2; i <= n; i++) {
        LL x, y;
        cin >> x >> y;
        G[x].push_back(make_pair(i, y));
    }
    dfs(1);
    cout << (f[1] >= 0 ? "YES\n" : "NO\n");
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值