线段树Ⅱ

线段树是一个大专题,有很多题可以写,以后可能会写线段树Ⅲ。

我的第一道黑题啊,至少有里程碑一般的意义啊……

\(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;
}
posted @ 2021-08-05 21:14  Feyn618  阅读(34)  评论(0编辑  收藏  举报