BZOJ2957: 楼房重建 题解

线段树的高级操作

首先肯定将每个点化成斜率来算

我们考虑在线段树的每个节点记录一个sum[k],表示如果当前只有这个节点范围内的那些楼房,完全不考虑这个区间之外的楼房,能看到多少个楼房

那么显然在单点更新的时候,更新的是线段树上一条链上的节点,那么没有被更新到的节点sum值是不会变的,所以每次更新只会有logn个sum被更新

考虑如果sum[k]的孩子的sum都已经计算好了,该如何更新sum[k]的值

首先,sum[k]显然应该包含sum[k<<1]的那些点,他们都应该被加进答案

定义函数calc(premax,cur)表示如果之间出现的最大的数是premax的话,我在cur这个区间内能看到多少个点

则sum[k]=sum[k<<1]+calc(max[k<<1],k<<1|1)

记k<<1|1为rson,我们考虑将rson再细分,考虑rson<<1和rson<<1|1,记k<<1区间的最大值为max1,rson<<1区间的最大值为max2

如果max1>=max2,那么rson<<1里面的点会被k<<1中的最大值全部挡住,所以rson<<1中的点都不会被计入答案,此时calc(max[k<<1],rson)=calc(max[k<<1],rson<<1|1)

如果max1<max2,那么calc(max[k<<1],rson)=calc(max[k<<1].rson<<1)+calc(max[rson<<1],rson<<1|1)

但是注意我们当前在计算sum[k],那么他的孩子们都已经被计算完了,所以后面的calc(maxn[rson<<1],rson<<1|1)实际上是不用利用函数算的,可以直接写成sum[rson]-sum[rson<<1],注意千万不能写成sum[rson<<1|1],因为单独算rson<<1|1的sum没有考虑到max[rson<<1]对答案的限制作用

所以max1<max2时,calc(max[k<<1],rson)=sum[rson]-sum[rson<<1]+calc(max[k<<1],rson<<1)

可以看到calc函数每次向孩子走,所以会在logn的时间内算出,calc函数在一次更新中被调用了logn次,所总复杂度为O(nlog2n)

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <utility>
#include <cctype>
#include <algorithm>
#include <bitset>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <deque>
#include <stack>
#include <cmath>
#define LL long long
#define LB long double
#define x first
#define y second
#define Pair pair<int,LL>
#define pb push_back
#define pf push_front
#define mp make_pair
#define LOWBIT(x) x & (-x)
using namespace std;
 
const int MOD=100003;
const LL LINF=2e16;
const int INF=2e9;
const int magic=348;
const double eps=1e-10;
 
inline int getint()
{
    char ch;int res;bool f;
    while (!isdigit(ch=getchar()) && ch!='-') {}
    if (ch=='-') f=false,res=0; else f=true,res=ch-'0';
    while (isdigit(ch=getchar())) res=res*10+ch-'0';
    return f?res:-res;
}

struct node
{
	int left,right;
	double maxn;int sum;
}tree[600048];

int n,q;

inline void build(int cur,int left,int right)
{
	tree[cur].left=left;tree[cur].right=right;
	tree[cur].maxn=-1;tree[cur].sum=0;
	if (left!=right)
	{
		int mid=(left+right)>>1;
		build(cur<<1,left,mid);
		build(cur<<1|1,mid+1,right);
	}
}

inline int calc(double cmp,int cur)
{
	if (tree[cur].left==tree[cur].right) return tree[cur].maxn>cmp?1:0;
	if (tree[cur<<1].maxn<=cmp)
		return calc(cmp,cur<<1|1);
	else
		return tree[cur].sum-tree[cur<<1].sum+calc(cmp,cur<<1);
}

inline void update(int cur,int pos,double newval)
{
	if (tree[cur].left==tree[cur].right)
	{
		tree[cur].maxn=newval;
		tree[cur].sum=1;
		return;
	}
	int mid=(tree[cur].left+tree[cur].right)>>1;
	if (pos<=mid) update(cur<<1,pos,newval); else update(cur<<1|1,pos,newval);
	tree[cur].maxn=max(tree[cur<<1].maxn,tree[cur<<1|1].maxn);
	tree[cur].sum=tree[cur<<1].sum+calc(tree[cur<<1].maxn,cur<<1|1);
}

int main ()
{
	int pos,height;double val;
	n=getint();q=getint();
	build(1,1,n);
	while (q--)
	{
		pos=getint();height=getint();
		val=double(height)/double(pos);
		update(1,pos,val);
		printf("%d\n",tree[1].sum);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值