Luogu P4198 / bzoj 2957 楼房重建 线段树

题目:  luogu4198          bzoj2957

小A的楼房外有一大片施工工地,工地上有N栋待建的楼房。每天,这片工地上的房子拆了又建、建了又拆。他经常无聊地看着窗外发呆,数自己能够看到多少栋房子。

为了简化问题,我们考虑这些事件发生在一个二维平面上。小A在平面上(0,0)点的位置,第i栋楼房可以用一条连接(i,0)和(i,Hi)的线段表示,其中Hi为第i栋楼房的高度。如果这栋楼房最高点与(0,0)的连线没有与之前的线段相交,那么这栋楼房就被认为是可见的。

施工队的建造总共进行了M天。初始时,所有楼房都还没有开始建造,它们的高度均为0。在第i天,建筑队将会将横坐标为Xi的房屋的高度变为Yi(高度可以比原来大—修建,也可以比原来小—拆除,甚至可以保持不变—建筑队这天什么事也没做)。请你帮小A数数每天在建筑队完工之后,他能看到多少栋楼房?

分析:

建楼的图大概这样……瞎画的


可以发现,斜率是该楼是否可见的关键。(好像是废话)(以下的“大”、“小”都指斜率)

现在可以大体想出做法:

每更改一次,看更改处的左边是否有比它大的,有的话此次更改对答案无贡献;没有的话再看右边,右边比它大的才可见。

具体要用到线段树优化。

维护区间最大值和区间上升序列长度即可。
#include<bits/stdc++.h>	
using namespace std;
int n,m;
double a[100010];
struct building{
	double mx;//区间最大值 
	int len;//区间上升序列长度 
}t[400010];
void pushup1(int x)
{
	t[x].mx=max(t[x<<1].mx ,t[x<<1|1].mx);//维护最大值 
}
int pushup2(double mxx,int x,int l,int r)//关键部分 
{
	if(t[x].mx<=mxx)	return 0;//对答案无贡献 
	if(a[l]>mxx)		return t[x].len; 
	if(l==r)	return a[l]>mxx;//若>,返回1;若<=,返回0 
	int s1=x<<1,s2=x<<1|1;//两个子区间 
	int mid=(l+r)>>1;
	if(t[s1].mx<=mxx)	return pushup2(mxx,s2,mid+1,r);
	else	return pushup2(mxx,s1,l,mid)+t[x].len-t[s1].len;
} 
void change(int x,int l,int r,int a,int b)
{
	if(l==r && l==a)
	{
		t[x].mx=(double)b/a;//斜率 
		t[x].len=1;
		return;
	}
	int mid=(l+r)>>1;
	if(a<=mid)	change(x<<1,l,mid,a,b);
	else 	change(x<<1|1,mid+1,r,a,b);//递归 
	pushup1(x);//维护区间最值 
	t[x].len=t[x<<1].len+pushup2(t[x<<1].mx,x<<1|1,mid+1,r);//维护区间上升序列长度 
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a[x]=(double)y/x;//斜率 
		change(1,1,n,x,y);//更改 
		printf("%d\n",t[1].len);
	}
	return 0;
} 

这是一道经典的组合数学题目,需要用到组合数的性质。 我们可以先考虑 $n=5$ 的情况。这时,一共有 $2^n=32$ 种可能的抛硬币的结果,其中正面朝上的硬币数为 $0,1,2,3,4,5$ 的情况分别有 $1,5,10,10,5,1$ 种。 接下来,我们考虑 $n$ 的任意情况。可以证明,当 $n$ 为偶数时,正面朝上的硬币数的种数与 $n=5$ 时是相同的;当 $n$ 为奇数时,正面朝上的硬币数的种数比 $n=5$ 时多一种。这是因为当抛硬币的次数为偶数时,正反面的数量是相等的,因此正面朝上的硬币数的种数与 $n=5$ 时相同;当抛硬币的次数为奇数时,正反面的数量不相等,因此正面朝上的硬币数的种数比 $n=5$ 时多一种。 因此,需要分别处理 $n$ 为奇数和偶数的情况。当 $n$ 为偶数时,正面朝上的硬币数的种数与 $n=5$ 时相同,因此答案为: $$ \sum_{i=0}^{n/2} \binom{n}{i} $$ 当 $n$ 为奇数时,正面朝上的硬币数的种数比 $n=5$ 时多一种,因此答案为: $$ \sum_{i=0}^{n/2} \binom{n}{i} + \sum_{i=0}^{n/2} \binom{n}{i+1} $$ 需要注意的是,当 $n$ 为 $0$ 时,只有一种可能的结果,即所有硬币都是反面朝上,因此答案为 $1$。 以下是一份参考代码,可以用于计算答案: ```c++ #include <iostream> #include <cmath> using namespace std; int main() { int n; cin >> n; if (n == 0) { cout << "1" << endl; } else { int ans = pow(2, n); if (n % 2 == 0) { for (int i = 0; i <= n / 2; i++) { ans -= 2 * pow(-1, i) * pow(2, n - i) * (1 << i) / (i + 1); } } else { for (int i = 0; i <= n / 2; i++) { ans -= 2 * pow(-1, i) * pow(2, n - i) * (1 << i) / (i + 1); } for (int i = 0; i <= n / 2; i++) { ans -= 2 * pow(-1, i) * pow(2, n - i - 1) * (1 << i) / (i + 1); } } cout << ans << endl; } return 0; } ``` 代码中使用了数学公式计算答案,其中 $\binom{n}{i}$ 使用了移项后再计算的方式,避免了复杂的组合数计算。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值