【uoj#228】基础数据结构练习题 线段树+均摊分析

摘要:
标题描述给出了一个长度为$n$的序列,并支持$m$操作。有三种操作:区间加法、区间开根和区间求和。对于原始的两个数字$a$和$b$,$sqrta$和$sqrtb$在生根后变为$sqrt$和$sqrtb$,它们的差值从$a-b$变为$sqrta-sqrtb$。还有$=a-b$,所以平方根之后的差值小于原始差值的平方根。因此,在一个间隔中生根$loglog$次后,不需要强制生根,可以直接缩短间隔。分析间隔添加操作:仅修改经过节点的势能,影响$log$节点,这些节点的势能将恢复为$loglog$。所以总的时间复杂度就是总势能$O$。

题目描述

给出一个长度为 $n$ 的序列,支持 $m$ 次操作,操作有三种:区间加、区间开根、区间求和。

$n,m,a_ile 100000$ 。


题解

线段树+均摊分析

对于原来的两个数 $a$ 和 $b$ ( $a>b$ ) ,开根后变成 $sqrt a$ 和 $sqrt b$ ,它们的差从 $a-b$ 变成了 $sqrt a-sqrt b$ 。

又有 $(sqrt a-sqrt b)(sqrt a+sqrt b)=a-b$ ,因此开方后的差小于原来差的开方。

而当区间差为 $0$ 或 $a=x^2,b=x^2-1$ 的 $1$ 时,区间开根就变成了区间减。

因此一个区间开根 $loglog(Max-Min)$ 次后就不需要暴力开根,直接区间减即可。

定义线段树节点势能为 $loglog(Max-Min)$ ,那么每次对 $[l,r]$ 开根就是将所有 $lle x,yle r$ ,且势能不为 $0$ 的节点 $[x,y]$ 的势能减 $1$ ,代价为势能减少总量。

分析区间加操作:只会修改到经过的节点的势能,影响 $log$ 个节点,将这些点的势能恢复为 $loglog(Max-Min)$ 。

因此总的时间复杂度就是总势能量 $O((n+mlog n)loglog a)$ 。

#include <cmath>
#include <cstdio>
#include <algorithm>
#define N 100010
#define lson l , mid , x << 1
#define rson mid + 1 , r , x << 1 | 1
using namespace std;
typedef long long ll;
ll sum[N << 2] , mx[N << 2] , mn[N << 2] , tag[N << 2];
inline void add(ll v , int l , int r , int x)
{
	sum[x] += v * (r - l + 1) , mx[x] += v , mn[x] += v , tag[x] += v;
}
inline void pushup(int x)
{
	sum[x] = sum[x << 1] + sum[x << 1 | 1];
	mx[x] = max(mx[x << 1] , mx[x << 1 | 1]);
	mn[x] = min(mn[x << 1] , mn[x << 1 | 1]);
}
inline void pushdown(int l , int r , int x)
{
	if(tag[x])
	{
		int mid = (l + r) >> 1;
		add(tag[x] , lson) , add(tag[x] , rson); 
		tag[x] = 0;
	}
}
inline void build(int l , int r , int x)
{
	if(l == r)
	{
		scanf("%lld" , &sum[x]) , mx[x] = mn[x] = sum[x];
		return;
	}
	int mid = (l + r) >> 1;
	build(lson) , build(rson);
	pushup(x);
}
inline void update(int b , int e , ll a , int l , int r , int x)
{
	if(b <= l && r <= e)
	{
		add(a , l , r , x);
		return;
	}
	pushdown(l , r , x);
	int mid = (l + r) >> 1;
	if(b <= mid) update(b , e , a , lson);
	if(e > mid) update(b , e , a , rson);
	pushup(x);
}
inline void change(int b , int e , int l , int r , int x)
{
	if(b <= l && r <= e && mx[x] - (ll)sqrt(mx[x]) == mn[x] - (ll)sqrt(mn[x]))
	{
		add((ll)sqrt(mx[x]) - mx[x] , l , r , x);
		return;
	}
	pushdown(l , r , x);
	int mid = (l + r) >> 1;
	if(b <= mid) change(b , e , lson);
	if(e > mid) change(b , e , rson);
	pushup(x);
}
inline ll query(int b , int e , int l , int r , int x)
{
	if(b <= l && r <= e) return sum[x];
	pushdown(l , r , x);
	int mid = (l + r) >> 1;
	ll ans = 0;
	if(b <= mid) ans += query(b , e , lson);
	if(e > mid) ans += query(b , e , rson);
	return ans;
}
int main()
{
	int n , m , opt , x , y;
	ll z;
	scanf("%d%d" , &n , &m);
	build(1 , n , 1);
	while(m -- )
	{
		scanf("%d%d%d" , &opt , &x , &y);
		if(opt == 1) scanf("%lld" , &z) , update(x , y , z , 1 , n , 1);
		else if(opt == 2) change(x , y , 1 , n , 1);
		else printf("%lld
" , query(x , y , 1 , n , 1));
	}
	return 0;
}

免责声明:文章转载自《【uoj#228】基础数据结构练习题 线段树+均摊分析》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇V-rep学习笔记:机器人模型创建3—搭建动力学模型Jupyter-NoteBook-你应该知道的N个小技巧下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

洛谷P3833

Description 树链剖分板子题 考查两种操作 A u v w 把 u 节点到 v 节点路径上所有节点权值加 w Q u 求以 u 为根节点的子树权值之和 首先需要了解线段树和 dfs 序,我这里没有很好的链接,不熟悉的再自行百度吧 另外了解树链剖分的思想(重儿子等等),否则会出很多千奇百怪的错误 树链剖分的构成 DFS1 来处理每个点的...

[POJ1195] Mobile phones(二维树状数组)

题目链接:http://poj.org/problem?id=1195 题意:四种操作: 0:初始化一个S*S的零矩阵 1:点(x,y)是值+A 2:查询一个子矩阵里所有数的和 3:退出 线段树由于不能在两棵树之间传递标记,所以这种求和的操作非常难处理。 改学了一下而为树状数组,发现可是比二维线段树简单多了。 记得之前曾经看过zkw线段树的ppt讲稿,好像...

hdu 4614 线段树 二分

题意:有n个花瓶,每个花瓶中只能放一朵花。 两种操作,一种是从A开始放F朵花,如果有的花瓶中已经有花则跳过这个花瓶,往下一个花瓶放; 输出这次放的花的左右端点。如果能放但是放不完f朵输出n就好了 第二种是将区间[A,B]之间花瓶中的花清空。输出这次总共清理出了多少支花。 思路:第二种很明显可以用线段树实现,问题在第一种如何用线段树实现 用二分 和 线段树...

codeforces 765 F Souvenirs 线段树+set

题意:多次询问区间内 两数差的绝对值的最小值 题解:离线询问则可以按照询问的l排序,倒着询问,倒着从r更新到l 每次更新i+1到n这个区间,保证这次的更新不会影响到下一次以及以后的更新。因为当两个区间出现覆盖时,l更小的那个区间的值一定小于等于另一个,画个图就可以明白。 #include <iostream> #include <cstd...

4.18 省选模拟赛 消息传递 树剖 倍增 线段树维护等比数列

把树剖和倍增 线段树的联系诠释的很完美。 题目意思:自行理解。 做法:设两个点x,y x能挡住y 且在k点处 那么至少的得到一个式子 tx+dx-dk<tx+dy-dk 从这个式子中可以发现 按tx+dx这个排序 前面的可以有可能挡住后面的 同时相等时题目中的要求小的编号优先。 我们考虑一个点挡住当前的这个点的去路 设sx表示x这个点当前不能通过的...

CF981E Addition on Segments(线段树分治+bitset)

观察本题,我们发现,如果某些操作每次都更新到了某个位置,那么我们可以通过维护bitset来发现这个对于这个位置来说的可能最大值 而且答案就是所有位置取一下或。因为我们发现对于每个位置,bitset上为1的那些数一定可以取到最大值。 但是这样过于暴力,我们发现这是一个区间操作,而区间操作一般和线段树相结合,因此采用线段树分治,先对这段操作影响到的区间打标记...