洛谷P2184 贪婪大陆
标签
- 染色问题
- 线段树
简明题意
- 类似区间染色,操作1给出L,R涂色,操作2询问L,R的颜色数量。唯一不同的是这里的颜色会覆盖。n、m <= 1e5
思路
- 首先是暴力涂色,然而你还需要记录每个节点具体的颜色,时间复杂度爆炸
- 我们开两个数组num_l, num_r分别记录哪些地方有左端点,哪些地方有右端点。我们可以发现,当我们查询[L,R]区间地雷种类数时,我们用当前总的地雷种数 - 在L的左边的右端点数-在R右边的左端点数,即可得到当前答案。(如果不理解,请仔细思考一番)。这里的左端点数和右端点数可以通过前面的num_l,num_r直接for循环。这样,修改操作是O(1)的,查询操作需要for循环预处理,再查询,是O(n)的。总体来说,最坏的时间复杂度为O( n 2 n^2 n2),最好的情况下为O(n),这样能拿到60分。接下来我们考虑优化
- 很显然,优化策略可以考虑线段树线段,区间查询,单点查询,写出来即可。
注意事项
- 如果要写两颗线段树,可以写一个结构体,实例出两个对象,就可以简化代码了
总结
- 类似容斥的题目。我们拘泥于一些容易维护的东西。这题要维护的东西显然和一般的题目不一样,他需要的是维护区间端点在任意一个区内的数量
AC代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 1e5 + 10;
struct Node
{
int l, r, num;
};
int n, m;
struct Seg_tree
{
Node tree[maxn * 4];
void build(int o, int l, int r)
{
tree[o].l = l, tree[o].r = r;
if (l == r)
return;
int mid = (l + r) / 2;
build(o * 2, l, mid);
build(o * 2 + 1, mid + 1, r);
}
void change(int o, int x)
{
tree[o].num++;
if (tree[o].l == tree[o].r)
return;
int mid = (tree[o].l + tree[o].r) / 2;
if (x <= mid)
change(o * 2, x);
else
change(o * 2 + 1, x);
}
int ask(int o, int l, int r)
{
if (l > n || r > n || l <= 0 || r <= 0) return 0;
if (l > r) return 0;
if (tree[o].l == l && tree[o].r == r)
return tree[o].num;
int mid = (tree[o].l + tree[o].r) / 2;
if (r <= mid)
return ask(o * 2, l, r);
else if (l > mid)
return ask(o * 2 + 1, l, r);
else
return ask(o * 2, l, mid) + ask(o * 2 + 1, mid + 1, r);
}
};
Seg_tree t1, t2;
void solve()
{
int cnt = 0;
scanf("%d%d", &n, &m);
t1.build(1, 1, n), t2.build(1, 1, n);
for (int i = 1; i <= m; i++)
{
int opt, l, r;
scanf("%d%d%d", &opt, &l, &r);
if (opt == 1)
cnt++, t1.change(1, l), t2.change(1, r);
else if (opt == 2)
printf("%d\n", cnt - t2.ask(1, 1, l - 1) - t1.ask(1, r + 1, n));
}
}
int main()
{
solve();
return 0;
}
双倍经验
- 无