BZOJ 2957 楼房重建
题意
:一个长度为
n
n
n的序列,有
m
m
m个操作。操作分为两类,① 令a[x] = y ② 查询全局的递增序列,能选就选。
(
1
≤
n
,
m
≤
1
0
5
)
(1\leq n,m\leq 10^5)
(1≤n,m≤105)
思路
:线段树节点维护
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;
}
总结
两题都是在区间合并时,二分计算右节点对父节点的贡献,只需对右节点不断递归即可求解。