【2019南昌邀请赛网络赛 B Greedy HOUHOU & BZOJ 2957 楼房重建】线段树+二分

BZOJ 2957 楼房重建

题意:一个长度为 n n n的序列,有 m m m个操作。操作分为两类,① 令a[x] = y ② 查询全局的递增序列,能选就选。 ( 1 ≤ n , m ≤ 1 0 5 ) (1\leq n,m\leq 10^5) (1n,m105)
思路:线段树节点维护 m a x n maxn maxn s u m sum sum s u m sum sum表示该节点对应区间的递增序列值之和, m a x n maxn maxn表示该节点对应区间节点中的最大值。
然后考虑如何进行区间合并。 m a x n [ n o w ] = m a x   (   m a x n [ l s [ n o w ] ] ,   m a x n [ r s [ n o w ] ]   ) maxn[now] = max\ (\ maxn[ls[now]],\ maxn[rs[now]]\ ) maxn[now]=max ( maxn[ls[now]], maxn[rs[now]] ) s u m [ n o w ] = s u m [ l s [ n o w ] ] + sum[now] = sum[ls[now]]+ sum[now]=sum[ls[now]]+右区间的贡献。所以我们考虑如何计算右区间的贡献。
新设一个函数 c a l c ( n o w , t p ) calc(now,tp) calc(now,tp),表示节点now中大于tp的数形成的一个递增序列的和。然后对于节点 n o w now now进行二分,如果 n o w now now左儿子的最大值大于 t p tp tp,则可以直接算出右节点的贡献,然后继续递归左节点,即 s u m [ n o w ] − s u m [ l s [ n o w ] ] sum[now]-sum[ls[now]] sum[now]sum[ls[now]],因为 n o w now now左儿子对右儿子的影响大于tp的影响。如果左儿子最大值小于 t p tp tp,则左儿子贡献值为 0 0 0,继续递归右儿子即可。
代码
#include <cstdio>
#include <iostream>
#define rep(i,a,b) for(int i = a; i <= b; i++)
typedef long long ll;
typedef double db;
const int N = 3*1e5+100;
using namespace std;

int n,m,ls[N],rs[N],rt,sz;
ll sum[N];
db maxn[N];

ll calc(int &now,int l,int r,db tp){ //计算now在tp条件下的贡献
	if(!now) now = ++sz;
	if(l == r) return (maxn[now]>tp?sum[now]:0ll);
	int mid = (l+r)>>1;
	if(maxn[ls[now]] > tp) return sum[now]-sum[ls[now]]+calc(ls[now],l,mid,tp);
	else return calc(rs[now],mid+1,r,tp);
}

void update(int &now,int l,int r,int pos,ll w){
	if(!now) now = ++sz;
	if(l == r){
		maxn[now] = (db)w/l;
		sum[now] = 1;
		return;
	}
	int mid = (l+r)>>1;
	if(pos <= mid) update(ls[now],l,mid,pos,w);
	else update(rs[now],mid+1,r,pos,w);
	maxn[now] = max(maxn[ls[now]],maxn[rs[now]]);
	sum[now] = sum[ls[now]]+calc(rs[now],mid+1,r,maxn[ls[now]]);
}

int main()
{
	scanf("%d%d",&n,&m);
	rep(i,1,m){
		int xx; ll yy;
		scanf("%d%lld",&xx,&yy);
		update(rt,1,n,xx,yy);
		printf("%lld\n",sum[rt]);
	}
	return 0;
}

南昌邀请赛网络赛 B-Greedy HOUHOU

题意:与楼房重建题意类似,但是求的是递减序列,而且询问的是区间 [ l , r ] [l,r] [l,r]的递减序列和。
思路:只需将维护内容的 m a x n maxn maxn改为 m i n n minn minn即可,然后解决一下区间查询的问题。继续使用刚才的 c a l c ( n o w , t p ) calc(now,tp) calc(now,tp)函数,计算节点 n o w now now在最小值为 t p tp tp下的贡献。依然是左右区间二分,如果左区间最小值小于 t p tp tp,则直接计算右区间贡献,然后递归左区间。如果左区间最小值大于 t p tp tp,则直接递归右区间。
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 1e5+100;
const int M = 3*1e5+100;
const ll inf = 1e13;
const db EPS = 1e-9;
using namespace std;

int n,q,rt,sz,ls[M],rs[M];
ll a[N],sum[M],minn[M],ans;

ll calc(int &now,int l,int r,ll tp){
	if(!now) now = ++sz;
	if(l == r) return (minn[now]<tp?minn[now]:0ll);
	int mid = (l+r)>>1;
	if(minn[ls[now]] < tp) return (sum[now]-sum[ls[now]]+calc(ls[now],l,mid,tp));
	else return calc(rs[now],mid+1,r,tp);
}

void update(int &now,int l,int r,int pos,ll w){
	if(!now) now = ++sz;
	if(l == r){
		sum[now] = minn[now] = w;
		return;
	}
	int mid = (l+r)>>1;
	if(pos <= mid) update(ls[now],l,mid,pos,w);
	else update(rs[now],mid+1,r,pos,w);
	minn[now] = min(minn[rs[now]],minn[ls[now]]);
	sum[now] = sum[ls[now]]+calc(rs[now],mid+1,r,minn[ls[now]]);
}

ll query(int &now,int l,int r,int pos1,int pos2,ll w){
	if(!now) now = ++sz;
	if(pos1 <= l && pos2 >= r){
		ans += calc(now,l,r,w);
		return minn[now];
	}
	int mid = (l+r)>>1;
	if(pos1 <= mid) w = min(w,query(ls[now],l,mid,pos1,pos2,w));
	if(pos2 > mid) w = min(w,query(rs[now],mid+1,r,pos1,pos2,w));
	return w; 
}

int main()
{
	int _; scanf("%d",&_);
	while(_--){
		rt = sz = 0;
		memset(ls,0,sizeof ls);
		memset(rs,0,sizeof rs);
		scanf("%d%d",&n,&q);
		rep(i,1,n){
			scanf("%lld",&a[i]);
			update(rt,1,n,i,a[i]);
		} 
		rep(i,1,q){
			int l,r,p,c; scanf("%d%d%d%d",&l,&r,&p,&c);
			ans = 0, query(rt,1,n,l,r,inf);
			printf("%lld\n",ans);
			if(p != 0 || c != 0) update(rt,1,n,p,c);
		}
	}
	return 0;
}

总结

两题都是在区间合并时,二分计算右节点对父节点的贡献,只需对右节点不断递归即可求解。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gene_INNOCENT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值