线段树Ⅱ
线段树是一个大专题,有很多题可以写,以后可能会写线段树Ⅲ。
我的第一道黑题啊,至少有里程碑一般的意义啊……
\(dp\) 方程不难想,主要是线段树优化部分太有趣了。
\[f_i=\min{(f_k+cost_{k,i}+c_i)}(j-1\le k\le i-1)
\]
看到在某个点之前的一段区间内选择决策点,还是线性方程,要么单调队列,要么斜率优化,还有就是数据结构。单调队列不现实,斜率优化不符合条件,那只能考虑数据结构了。
而要在一段区间内找出最小的元素,想到了线段树。可以\(O(\log N)\)查询,接下来要思考的就是修改了。
不得不说修改特别巧妙,它是在随着 \(i\) 的增加不断更新 \(cost_{k,i}\) ,又因为整个过程中每个点位只会变化一次,那么就可以事先预处理一下每个 \(i\) 能使哪些 \(cost_{k,i}\) ,就可以正常修改了。
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int N=20010;
const int M=105;
const int maxn=1e9;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
wh*=f;return;
}
inline int min(int s1,int s2){
return s1<s2?s1:s2;
}
int m,able;
int a[N],c[N],s[N],w[N];//位置,费用,覆盖范围,补偿
int dp[N][M];//dp[i][j]前i个村庄有j个基站且最后一个在村庄i的费用
//dp[i][j]不包括i以后的赔偿
int al[N],ar[N];//修基站能覆盖村庄i的村庄为[al[i],ar[i]]
vector<int>ve[N];
//线段树,支持区间最小值查询和区间加修改
#define lc (wh<<1)
#define rc (wh<<1|1)
#define mid (t[wh].l+t[wh].r>>1)
struct node{
int l,r,lazy,minn;
}t[N*4];
inline void pushup(int wh){
t[wh].minn=min(t[lc].minn,t[rc].minn);
}
inline void pushnow(int wh,int val){
t[wh].minn+=val;
t[wh].lazy+=val;
}
inline void pushdown(int wh){
if(t[wh].lazy){
pushnow(lc,t[wh].lazy);
pushnow(rc,t[wh].lazy);
t[wh].lazy=0;
}
}
void build(int wh,int l,int r,int nr){
t[wh].l=l,t[wh].r=r,t[wh].lazy=0;
if(l==r){
t[wh].minn=dp[l][nr];
return;
}
build(lc,l,mid,nr);
build(rc,mid+1,r,nr);
pushup(wh);
}
void change(int wh,int wl,int wr,int val){
if(wl>wr)return;
if(wl<=t[wh].l&&t[wh].r<=wr){
pushnow(wh,val);
return;
}
pushdown(wh);
if(wl<=mid)change(lc,wl,wr,val);
if(wr>mid)change(rc,wl,wr,val);
pushup(wh);
return;
}
int work(int wh,int wl,int wr){
if(wl<=t[wh].l&&t[wh].r<=wr){
return t[wh].minn;
}
int an=maxn;
pushdown(wh);
if(wl<=mid)an=min(an,work(lc,wl,wr));
if(wr>mid)an=min(an,work(rc,wl,wr));
pushup(wh);
return an;
}
#undef lc
#undef rc
#undef mid
signed main(){
read(m);read(able);
for(int i=2;i<=m;i++)read(a[i]);
for(int i=1;i<=m;i++)read(c[i]);
for(int i=1;i<=m;i++)read(s[i]);
for(int i=1;i<=m;i++)read(w[i]);
//创造一个理想村,便于统计答案
//距离无限,赔偿无限,只有自己能覆盖自己,修建基站免费
//则最优情况理想存肯定修了基站
m++;able++;
a[m]=maxn;
c[m]=0;
s[m]=0;
w[m]=maxn;
//预处理al和ar
for(int i=1;i<=m;i++){
al[i]=lower_bound(a+1,a+m+1,a[i]-s[i])-a;
ar[i]=lower_bound(a+1,a+m+1,a[i]+s[i])-a;
if(a[i]+s[i]<a[ar[i]])ar[i]--;
//ve[i]:储存可以被i覆盖却不能被i+1覆盖的村庄编号
ve[ar[i]].push_back(i);
}
int ans=maxn,now=0;
//处理只修建一座基站的情况
for(int i=1;i<=m;i++){
//now代表i之前覆盖不到的村子所需赔偿
dp[i][1]=now+c[i];
//i每加一个,now就会加上那些最远只能被i覆盖到的村子的赔偿
for(int j=0;j<ve[i].size();j++){
now+=w[ve[i][j]];
}
}
ans=min(ans,dp[m][1]);
//进行后续dp
for(int j=2;j<=able;j++){
build(1,1,m,j-1);
for(int i=j;i<=m;i++){
dp[i][j]=work(1,j-1,i-1)+c[i];
for(int k=0;k<ve[i].size();k++){
change(1,1,al[ve[i][k]]-1,w[ve[i][k]]);
}
}
ans=min(ans,dp[m][j]);
}
printf("%d",ans);
return 0;
}
裸的线段树题目。但裸的线段树不等于线段树模板,因为裸的线段树代表它就是单纯地考这个数据结构,没有和其它东西结合起来。
这道题要做的第一步是把坐标转换成与原点连线的斜率,把问题转换成求区间最长的上升序列(必须包含区间第一个元素)的长度。
单点修改,连 \(lazy\) 都不用了,唯一比较麻烦的是 \(pushup\) 。此时由于题目的限制,我们可以不强求 \(O(1)\) 合并区间信息,而是放宽一些。
然后就可以了。
#include<cstdio>
//#define zczc
const int N=100010;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
wh*=f;return;
}
double a[N];
#define lc (wh<<1)
#define rc (wh<<1|1)
#define mid (t[wh].l+t[wh].r>>1)
inline double max(double s1,double s2){
return s1<s2?s2:s1;
}
struct node{
int data,l,r;
double maxn;
}t[N*4];
inline void pushup1(int wh){
t[wh].maxn=max(t[lc].maxn,t[rc].maxn);
return;
}
inline int pushup2(int wh,double val){
if(t[wh].l==t[wh].r)return t[wh].maxn>val;
if(t[wh].maxn<val)return 0;
if(a[t[wh].l]>val)return t[wh].data;
if(t[lc].maxn<val)return pushup2(rc,val);
else return t[wh].data-t[lc].data+pushup2(lc,val);
}
void build(int wh,int l,int r){
t[wh].l=l,t[wh].r=r;
if(l==r)return;
build(lc,l,mid);
build(rc,mid+1,r);
return;
}
void change(int wh,int pl,double val){
if(t[wh].l==t[wh].r){
t[wh].maxn=val;
t[wh].data=1;
return;
}
if(pl<=mid)change(lc,pl,val);
else change(rc,pl,val);
pushup1(wh);
t[wh].data=t[lc].data+pushup2(rc,t[lc].maxn);
return;
}
#undef lc
#undef rc
#undef mid
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
int m,n,x,y;
read(m);read(n);
build(1,1,m);
while(n--){
read(x);read(y);
double k=(double)y/x;
a[x]=k;
change(1,x,k);
printf("%d\n",t[1].data);
}
return 0;
}
玄学题目。
如果只有操作1,3,4,就是普通线段树;加上了操作2,就要使用之前老师讲过的方法,顺便维护区间次大值和最大值的数量,传说经过什么叫做势能分析的东西就可以把复杂度压下来。
但是还有一个操作5。问题一下子就复杂了。调了一个下午才把这道题调出来。见注释。
#include<cstdio>
#define ll long long
//#define zczc
const int N=500010;
const int Max=1e9;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
wh*=f;return;
}
inline int max(int s1,int s2){
return s1<s2?s2:s1;
}
inline void check(int &s1,int s2){
if(s1<s2)s1=s2;
return;
}
int m,n;
#define lc (wh<<1)
#define rc (wh<<1|1)
#define mid (t[wh].l+t[wh].r>>1)
#define num (t[wh].r-t[wh].l+1)
struct node{
int l,r;
int maxn,cnt,cmaxn,his_maxn;
int max_lazy,fmax_lazy;
int max_his_lazy,fmax_his_lazy;
ll data;
}t[N<<2];
/*
maxn是区间最大值,cmaxn是区间严格次小值
cnt是区间最大值的个数,his_maxn是区间历史最大值
max_lazy是区间最大值要加的lazy
fmax_lazy是区间非最大值要加的lazy
max_his_lazy是区间最大值(现在)的历史最大值要加的lazy
fmax_his_lazy是区间非最大值的历史最大值要加的lazy
data是现在的区间和
*/
inline void pushup(int wh){
t[wh].maxn=max(t[lc].maxn,t[rc].maxn);
t[wh].his_maxn=max(t[lc].his_maxn,t[rc].his_maxn);
t[wh].data=t[lc].data+t[rc].data;
//work cmaxn&cnt
//对左儿子和右儿子的maxn和cmaxn分类讨论,更新当前节点的maxn
if(t[lc].maxn==t[rc].maxn){
t[wh].cmaxn=max(t[lc].cmaxn,t[rc].cmaxn);
t[wh].cnt=t[lc].cnt+t[rc].cnt;
}
else if(t[lc].maxn>t[rc].maxn){
t[wh].cmaxn=max(t[lc].cmaxn,t[rc].maxn);
t[wh].cnt=t[lc].cnt;
}
else{
t[wh].cmaxn=max(t[lc].maxn,t[rc].cmaxn);
t[wh].cnt=t[rc].cnt;
}
return;
}
inline void pushnow(int wh,int val1,int val2,int val3,int val4){
//4个val分别对应四个lazy
//work data
//更新区间和
t[wh].data+=1ll*val1*t[wh].cnt;//最大值有cnt个
t[wh].data+=1ll*val2*(num-t[wh].cnt);//非最大值有num-cnt个
//work his_maxn&lazy
check(t[wh].his_maxn,t[wh].maxn+val3);
//用当前最大值加最大值的历史最大值lazy更新当前区间的历史最大值
check(t[wh].max_his_lazy,t[wh].max_lazy+val3);
//用当前最大值lazy加上最大值历史最大值lazy更新当前区间的最大值历史最大值
check(t[wh].fmax_his_lazy,t[wh].fmax_lazy+val4);
//同上
//work maxn&maxn_his
t[wh].max_lazy+=val1;
t[wh].maxn+=val1;
//work cmaxn&fmax_lazy
t[wh].fmax_lazy+=val2;
t[wh].cmaxn+=val2;
return;
}
inline void pushdown(int wh){
int big=max(t[lc].maxn,t[rc].maxn);
if(t[lc].maxn==big){
pushnow(lc,t[wh].max_lazy,t[wh].fmax_lazy,t[wh].max_his_lazy,t[wh].fmax_his_lazy);
}
else{
pushnow(lc,t[wh].fmax_lazy,t[wh].fmax_lazy,t[wh].fmax_his_lazy,t[wh].fmax_his_lazy);
}
if(t[rc].maxn==big){
pushnow(rc,t[wh].max_lazy,t[wh].fmax_lazy,t[wh].max_his_lazy,t[wh].fmax_his_lazy);
}
else{
pushnow(rc,t[wh].fmax_lazy,t[wh].fmax_lazy,t[wh].fmax_his_lazy,t[wh].fmax_his_lazy);
}
t[wh].max_his_lazy=0;
t[wh].max_lazy=0;
t[wh].fmax_his_lazy=0;
t[wh].fmax_lazy=0;
return;
}
//建树,边递归边读入
void build(int wh,int l,int r){
t[wh].l=l,t[wh].r=r;
if(l==r){
int in;read(in);
t[wh].data=t[wh].his_maxn=t[wh].maxn=in;
t[wh].cmaxn=-Max;
t[wh].cnt=1;
return;
}
build(lc,l,mid);
build(rc,mid+1,r);
pushup(wh);
return;
}
//区间加
void change_sum(int wh,int wl,int wr,int val){
if(wl<=t[wh].l&&t[wh].r<=wr){
pushnow(wh,val,val,val,val);
return;
}
pushdown(wh);
if(wl<=mid)change_sum(lc,wl,wr,val);
if(wr>mid)change_sum(rc,wl,wr,val);
pushup(wh);
return;
}
//区间比较
void change_min(int wh,int wl,int wr,int val){
if(wl<=t[wh].l&&t[wh].r<=wr){
if(val>=t[wh].maxn)return;
else if(val>=t[wh].cmaxn){
pushnow(wh,val-t[wh].maxn,0,val-t[wh].maxn,0);
return;
}
}
pushdown(wh);
if(wl<=mid)change_min(lc,wl,wr,val);
if(wr>mid)change_min(rc,wl,wr,val);
pushup(wh);
return;
}
//求区间和
ll work_sum(int wh,int wl,int wr){
if(wl<=t[wh].l&&t[wh].r<=wr){
return t[wh].data;
}
ll an=0;
pushdown(wh);
if(wl<=mid)an+=work_sum(lc,wl,wr);
if(wr>mid)an+=work_sum(rc,wl,wr);
pushup(wh);
return an;
}
//求区间最大值
int work_max(int wh,int wl,int wr){
if(wl<=t[wh].l&&t[wh].r<=wr){
return t[wh].maxn;
}
int an=-Max;
pushdown(wh);
if(wl<=mid)check(an,work_max(lc,wl,wr));
if(wr>mid)check(an,work_max(rc,wl,wr));
pushup(wh);
return an;
}
//求区间历史最大值
int work_his_max(int wh,int wl,int wr){
if(wl<=t[wh].l&&t[wh].r<=wr){
return t[wh].his_maxn;
}
int an=-Max;
pushdown(wh);
if(wl<=mid)check(an,work_his_max(lc,wl,wr));
if(wr>mid)check(an,work_his_max(rc,wl,wr));
pushup(wh);
return an;
}
#undef lc
#undef rc
#undef mid
#undef num
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
int op,l,r,val;
read(m);read(n);
build(1,1,m);
while(n--){
read(op);
switch(op){
case 1:
read(l);read(r);read(val);
change_sum(1,l,r,val);
break;
case 2:
read(l);read(r);read(val);
change_min(1,l,r,val);
break;
case 3:
read(l);read(r);
printf("%lld\n",work_sum(1,l,r));
break;
case 4:
read(l);read(r);
printf("%d\n",work_max(1,l,r));
break;
case 5:
read(l);read(r);
printf("%d\n",work_his_max(1,l,r));
break;
}
}
return 0;
}
一如既往,万事胜意