使一整段区间内的东西变得一样,数据随机。
在具有区间赋值操作,区间统计操作,以及最好保证数据随机的情况下在时空复杂度上把线段树吊起来打。
珂朵莉树的各种操作的总体复杂度始终为O(NlogN),这会吊打某些常数大、附加工作会影响总体复杂度的线段树算法。
typedef bool type;
struct Node //每个结点代表一个闭区间
{
unsigned int l;
unsigned int r;
mutable type data; //当前区间统一的类型和数值,data需要mutable修饰,这样可以在set中利用迭代器修改它
Node(unsigned int a, unsigned int b = 0, type c = 0); //定义构造函数
bool operator <(const Node &a) const //由于使用set来存储Node,所以需要重载小于号,使其按照左端点排序
{
return l < a.l;
}
};
Node::Node(unsigned int a, unsigned int b, type c) //构造函数
{
l = a;
r = b;
data = c;
}
set <Node> s; //没有初始化的珂朵莉树
void init() //初始化珂朵莉树
{
for (int i = 0; i < n; ++i) //通过给定数据,向其中不断插入区间长度为1的区间来完成初始化
{
static type temp = 0;
cin >> temp;
s.insert(Node(i, i, temp));
}
s.insert(Node(n, n, 0)); //在末尾多插入一个点
}
set <Node>::iterator split(unsigned int pos) //分裂操作:将包含位置pos的区间[l, r]分裂成[l, pos - 1]和[pos, r],并返回后者的迭代器
{
set <Node>::iterator it = s.lower_bound(Node(pos)); //利用lower_bound()函数在set中查到左端点位置>=pos的结点
if (it != s.end() && it->l == pos) //如果这个结点的左端点位置正是pos,那么无需分裂,直接返回
return it;
it--; //如果不是,那么必然大于pos,则包含位置pos的结点是上一个结点,it--
//暴力分裂
unsigned int l = it->l, r = it->r;
type data = it->data;
s.erase(it);
//插入
s.insert(Node(l, pos - 1, data));
return s.insert(Node(pos, r, data)).first; //返回[pos, r]值的迭代器
}
/*
//此时如果想使用区间[l, r]中的数据
set <Node>::iterator it2 = split(r + 1), it1 = split(l); //必须先声明it2再声明it1,否则根据split中的erase操作,迭代器it1可能会失效(因为it1所属的结点可能被删除了)
for (; it1 != it2; ++it1)
{
//具体操作
}
*/
void assign(unsigned int l, unsigned int r, type val) // 区间赋值(既然一个区间内所有的值全都一样了,那么在珂朵莉树中这个区间就可以只用一个结点来表示)
{
set <Node>::iterator it2 = split(r + 1), it1 = split(l);
s.erase(it1, it2); //这个区间里所有结点全被删除
s.insert(Node(l, r, val)); //使用一个新的结点来代替
return ;
}
//assign的区间长度在随机数据下的期望为 N/3
//assign在赋值之余还可以顺便做做区间统计之类的,视情况而定
例题:https://ac.nowcoder.com/acm/contest/30184/C
#include <iostream>
#include <cstdio>
#include <queue>
#include <deque>
#include <stack>
#include <vector>
#include <algorithm>
#include <set>
#include <bitset>
#include <unordered_set>
#include <memory.h>
#include <string>
#include <cstring>
#include <cmath>
#include <map>
#include <unordered_map>
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define x first
#define y second
#define all(a) a.begin(), a.end()
//#define int long long
#define bug(a) cout << a << " *bug1*" << endl
#define bugg(a, b) cout << a << " " << b << " *bug2*" << endl
#define buggg(a, b, c) cout << a << " " << b << " " << c << " *bug3*" << endl
using namespace std;
typedef long long LL;
typedef pair<LL, LL> PLL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
const int N = 1e5 + 5, mod = 1e9 + 7, INF = 0x3f3f3f3f;
typedef LL type;
struct Node //每个结点代表一个闭区间
{
unsigned int l;
unsigned int r;
mutable type data; //当前区间统一的类型和数值,data需要mutable修饰,这样可以在set中利用迭代器修改它
Node(unsigned int a, unsigned int b = 0, type c = 0); //定义构造函数
bool operator <(const Node &a) const //由于使用set来存储Node,所以需要重载小于号,使其按照左端点排序
{
return l < a.l;
}
};
Node::Node(unsigned int a, unsigned int b, type c) //构造函数
{
l = a;
r = b;
data = c;
}
set <Node> s; //没有初始化的珂朵莉树
/*
void init() //初始化珂朵莉树
{
for (int i = 0; i < n; ++i) //通过给定数据,向其中不断插入区间长度为1的区间来完成初始化
{
static type temp = 0;
cin >> temp;
s.insert(Node(i, i, temp));
}
s.insert(Node(n, n, 0)); //在末尾多插入一个点
}
*/
set <Node>::iterator split(unsigned int pos) //分裂操作:将包含位置pos的区间[l, r]分裂成[l, pos - 1]和[pos, r],并返回后者的迭代器
{
set <Node>::iterator it = s.lower_bound(Node(pos)); //利用lower_bound()函数在set中查到左端点位置>=pos的结点
if (it != s.end() && it->l == pos) //如果这个结点的左端点位置正是pos,那么无需分裂,直接返回
return it;
it--; //如果不是,那么必然大于pos,则包含位置pos的结点是上一个结点,it--
//暴力分裂
unsigned int l = it->l, r = it->r;
type data = it->data;
s.erase(it);
//插入
s.insert(Node(l, pos - 1, data));
return s.insert(Node(pos, r, data)).first; //返回[pos, r]值的迭代器
}
/*
//此时如果想使用区间[l, r]中的数据
set <Node>::iterator it2 = split(r + 1), it1 = split(l); //必须先声明it2再声明it1,否则根据split中的erase操作,迭代器it1可能会失效(因为it1所属的结点可能被删除了)
for (; it1 != it2; ++it1)
{
//具体操作
}
*/
void assign(unsigned int l, unsigned int r, type val) // 区间赋值(既然一个区间内所有的值全都一样了,那么在珂朵莉树中这个区间就可以只用一个结点来表示)
{
set <Node>::iterator it2 = split(r + 1), it1 = split(l);
s.erase(it1, it2); //这个区间里所有结点全被删除
s.insert(Node(l, r, val)); //使用一个新的结点来代替
return ;
}
//assign的区间长度在随机数据下的期望为 N/3
//assign在赋值之余还可以顺便做做区间统计之类的,视情况而定
LL querysum(unsigned int l, unsigned int r)
{
LL res = 0;
set <Node>::iterator it2 = split(r + 1), it1 = split(l); //必须先声明it2再声明it1,否则根据split中的erase操作,迭代器it1可能会失效(因为it1所属的结点可能被删除了)
for (; it1 != it2; ++it1)
res += (it1->r - it1->l + 1) * it1->data;
return res;
}
LL solve(int l, int r)
{
LL ans = 0;
set <Node>::iterator it2 = split(r + 1), it1 = split(l);
set <LL> temp;
for (; it1 != it2; ++it1)
temp.insert(it1->data);
for (auto &it:temp)
if (it != 0)
ans++;
return ans;
}
int main()
{
int n, q, op, l, r;
scanf("%d%d", &n, &q);
s.insert(Node(1, n, 1));
while (q--)
{
scanf("%d%d%d", &op, &l, &r);
if (op == 1)
assign(l, r, 1);
if (op == 2)
{
LL temp = querysum(l, r);
assign(l, r - 1, 0);
assign(r, r, temp);
}
if (op == 3)
printf("%lld\n", solve(l, r));
}
return 0;
}