珂朵莉树ODT(基于std::set的暴力玄学数据结构)

使一整段区间内的东西变得一样,数据随机。

在具有区间赋值操作,区间统计操作,以及最好保证数据随机的情况下在时空复杂度上把线段树吊起来打。

珂朵莉树的各种操作的总体复杂度始终为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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值