[IOI2018]会议——分治+线段树

时间:2022-05-25 19:28:03

题目链接:

[IOI2018]meetings

题目大意:有$n$座山峰,每座山峰有一个高度,有$q$次询问,每次需要确定一个开会山峰使$[l,r]$所有山峰上的人都前往开会山峰,一个山峰的人去开会的代价为从这个山峰到开会山峰区间内山峰高度的最大值,对于每次询问求最小代价和。

还是按照编号都从$1$开始讲。

可以发现对于一个询问如果确定了开会地址那么答案只和每个点到开会点区间最大值有关。

而题目又没有强制在线,我们可以按区间最大值来分治。

我们设对于区间$[l,r]$的答案是$ans(l,r)$,区间中的最大值位于$mid$处(即$h[mid]$是区间最大值)。

那么显然答案选定的开会点一定在mid的左侧或右侧,$ans(l,r)=min{ans(l,mid-1)+h[mid]*(r-mid+1),ans(mid+1,r)+h[mid]*(mid-l+1)}$。

即如果开会点在$mid$左边那么$mid$及右边所有点到开会点的代价都是$h[mid]$,如果开会点在$mid$右边那么$mid$及左边所有点到开会点的代价都是$h[mid]$。

因此,$ans(l,r)$要由$ans(l,mid-1)$与$ans(mid+1,r)$合并而来。

那么我们可以确定一个大致思路:找出当前区间最大值位置然后分治左右区间,在得出左右区间的答案之后再处理询问区间最大值是当前区间最大值的询问,然后再递归回上一些层。

区间最大值很好求,用ST表维护一下即可,那么对于每个区间的ans怎么维护?

对于左端点为l的区间难道要同时维护出$ans(l,l),ans(l,l+1),ans(l,l+2)$……吗?

显然并不需要,我们发现对于每一次递归的区间$[l,r]$,需要用到左端点为$l$的$ans$只有$ans(l,mid-1)$和$ans(l,r)$。

其中前者对应处理当前层询问前递归合并上来的答案,后者则对应处理完当前层询问后需要递归合并上去的答案。

也就是说对于一个点$x$,在同一时刻只需要维护它作为一个区间左端点和右端点时这两个答案。

那么我们就可以用线段树来实现,对于线段树上每个叶子结点维护$lm$和$rm$两个信息,分别表示这个点是区间左/右端点时的区间答案。

因为无法确定每个点在回溯到上一层后会向左扩展还是向右扩展即不确定每个点位于上一层最高点的右边还是左边,所以我们开两棵线段树分别维护这两种情况。其中每棵线段树只维护$lm$和$rm$中的一种。

总结一下大体思路:对于当前区间找到区间最大值位置并递归左右子区间,回溯时处理当前区间需要处理的询问,对于每个$ans(x,mid-1)(l<=x<=mid-1)$更新为$ans(x,r)$;对于每个$ans(mid+1,x)(mid+1<=x<=r)$更新为$ans(l,x)$。

因为每个点被当做区间最大值一次且每个询问被处理一次,所以时间复杂度是O((n+q)logn)。

如果还是不太明白可以看代码的具体实现。

#include"meetings.h"
#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<stack>
#include<cstdio>
#include<vector>
#include<bitset>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
int n,q;
int h[750010];
int ql[750010];
int qr[750010];
int f[750010][21];
int g[750010][21];
int ln[750010];
vector<int>pos[750010];
ll res[750010];
struct miku
{
ll lm[4000010];
ll rm[4000010];
ll k[4000010];
ll b[4000010];
int a[4000010];
void cover(int rt)
{
a[rt]=1;
lm[rt]=rm[rt]=k[rt]=b[rt]=0;
}
void add(int rt,int l,int r,ll K,ll B)
{
k[rt]+=K;
b[rt]+=B;
lm[rt]+=K*l+B;
rm[rt]+=K*r+B;
}
void pushup(int rt)
{
lm[rt]=lm[rt<<1];
rm[rt]=rm[rt<<1|1];
}
void pushdown(int rt,int l,int r)
{
int mid=(l+r)>>1;
if(a[rt])
{
a[rt]=0;
cover(rt<<1);
cover(rt<<1|1);
}
if(k[rt]||b[rt])
{
add(rt<<1,l,mid,k[rt],b[rt]);
add(rt<<1|1,mid+1,r,k[rt],b[rt]);
k[rt]=b[rt]=0;
}
}
void change(int rt,int l,int r,int L,int R,ll val)
{
if(L<=l&&r<=R)
{
add(rt,l,r,0,val);
return ;
}
int mid=(l+r)>>1;
pushdown(rt,l,r);
if(L<=mid)
{
change(rt<<1,l,mid,L,R,val);
}
if(R>mid)
{
change(rt<<1|1,mid+1,r,L,R,val);
}
pushup(rt);
}
void merge(int rt,int l,int r,int L,int R,ll K,ll B)
{
if(L<=l&&r<=R)
{
ll LV=K*l+B;
ll RV=K*r+B;
if(LV>=lm[rt]&&RV>=rm[rt])
{
return ;
}
if(LV<=lm[rt]&&RV<=rm[rt])
{
cover(rt);
add(rt,l,r,K,B);
return ;
}
}
pushdown(rt,l,r);
int mid=(l+r)>>1;
if(L<=mid)
{
merge(rt<<1,l,mid,L,R,K,B);
}
if(R>mid)
{
merge(rt<<1|1,mid+1,r,L,R,K,B);
}
pushup(rt);
}
ll query_left(int rt,int l,int r,int x)
{
if(l==r)
{
return lm[rt];
}
pushdown(rt,l,r);
int mid=(l+r)>>1;
if(x<=mid)
{
return query_left(rt<<1,l,mid,x);
}
else
{
return query_left(rt<<1|1,mid+1,r,x);
}
}
ll query_right(int rt,int l,int r,int x)
{
if(l==r)
{
return rm[rt];
}
pushdown(rt,l,r);
int mid=(l+r)>>1;
if(x<=mid)
{
return query_right(rt<<1,l,mid,x);
}
else
{
return query_right(rt<<1|1,mid+1,r,x);
}
}
}s,t;
int cmp(int l,int r)
{
int len=ln[r-l+1];
if(f[l][len]>=f[r-(1<<len)+1][len])
{
return g[l][len];
}
else
{
return g[r-(1<<len)+1][len];
}
}
void solve(int l,int r)
{
if(l>r)
{
return ;
}
int mid=cmp(l,r);
solve(l,mid-1);
solve(mid+1,r);
int len=pos[mid].size();
for(int i=0;i<len;i++)
{
int now=pos[mid][i];
res[now]=1ll*h[mid]*(qr[now]-ql[now]+1);
if(ql[now]<mid)
{
res[now]=min(res[now],s.query_left(1,1,n,ql[now])+1ll*h[mid]*(qr[now]-mid+1));
}
if(qr[now]>mid)
{
res[now]=min(res[now],t.query_right(1,1,n,qr[now])+1ll*h[mid]*(mid-ql[now]+1));
}
}
ll sx=h[mid];
ll tx=h[mid];
if(l<mid)
{
tx+=t.query_right(1,1,n,mid-1);
}
if(r>mid)
{
sx+=s.query_left(1,1,n,mid+1);
}
s.change(1,1,n,mid,mid,sx);
t.change(1,1,n,mid,mid,tx);
if(l<mid)
{
s.change(1,1,n,l,mid-1,1ll*h[mid]*(r-mid+1));
s.merge(1,1,n,l,mid-1,-1ll*h[mid],sx+1ll*mid*h[mid]);
}
if(r>mid)
{
t.change(1,1,n,mid+1,r,1ll*h[mid]*(mid-l+1));
t.merge(1,1,n,mid+1,r,1ll*h[mid],tx-1ll*mid*h[mid]);
}
}
vector<ll> minimum_costs(vector<int> H, vector<int> L,vector<int> R)
{
n=H.size();
q=L.size();
for(int i=1;i<=n;i++)
{
h[i]=H[i-1];
f[i][0]=h[i];
g[i][0]=i;
}
for(int i=1;i<=q;i++)
{
ql[i]=L[i-1];
qr[i]=R[i-1];
ql[i]++;
qr[i]++;
}
for(int i=2;i<=n;i++)
{
ln[i]=ln[i>>1]+1;
}
for(int j=1;j<=20;j++)
{
for(int i=1;i<=n;i++)
{
if(i+(1<<j)-1>n)
{
break;
}
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
if(f[i][j]==f[i][j-1])
{
g[i][j]=g[i][j-1];
}
else
{
g[i][j]=g[i+(1<<(j-1))][j-1];
}
}
}
for(int i=1;i<=q;i++)
{
pos[cmp(ql[i],qr[i])].push_back(i);
}
solve(1,n);
return vector<ll>(res+1,res+q+1);
}