线段树好神仙

P5069「Ynoi2015」纵使日薄西山

题目
首先注意到一个结论

i i i 处进行 ( a i − 1 , a i , a i + 1 ) (a_{i-1},a_i,a_{i+1}) (ai1,ai,ai+1) 减一的操作一直进行到 a i = 0 a_i=0 ai=0,不会改变最终答案。
证明:在 i i i 处进行操作不会影响 ( a i − 1 , a i , a i + 1 ) (a_{i-1},a_i,a_{i+1}) (ai1,ai,ai+1) 的大小关系。

所以只需要关注操作中心的集合就可以了。然后我们考虑用线段树维护。
为了方便处理,我们先将相邻两个数对应区间的答案作为线段树叶子结点对应的区间。然后在线段树上维护一个类似 dp 的数组 r e s [ 0 / 1 / 2 ] [ 0 / 1 / 2 ] res[0/1/2][0/1/2] res[0/1/2][0/1/2],表示区间最左端和最右端的数 没有被覆盖 / 被覆盖 / 是极大值。
然后类似 dp 地在线段树上合并。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<map>
#include<queue>
#define int long long
#define lc note<<1
#define rc note<<1|1
using namespace std;
const int N=1e5+5;
int n,m,x,y;
int a[N];
inline int read()
{
	int s=0,t=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
	return s*t;
}
struct matrix
{
	int cnt[5][5];
	int val[5];
};
inline matrix operator + (matrix p,matrix q)
{
	matrix res;
	memset(res.cnt,-1,sizeof(res.cnt));
	for(int i=0;i<=2;++i)
		for(int j=0;j<=2;++j)
			for(int k=0;k<=2;++k)
				for(int l=0;l<=2;++l)
				{
					if(j+k<2) continue;
					if(!j&&k==2&&!(p.val[2]<q.val[1])) continue;
					if(j==2&&!k&&!(p.val[2]>=q.val[1])) continue;
					if(p.cnt[i][j]==-1||q.cnt[k][l]==-1) continue;
					if(res.cnt[i][l]!=-1)
						res.cnt[i][l]=min(res.cnt[i][l],p.cnt[i][j]+q.cnt[k][l]);
					else res.cnt[i][l]=p.cnt[i][j]+q.cnt[k][l];
				}
	res.val[1]=p.val[1];
	res.val[2]=q.val[2];
	return res;
}
struct Tree
{
	int left,right;
	matrix res;
}s[N<<2];
inline void push_up(int note){s[note].res=s[lc].res+s[rc].res;}
inline void build(int note,int l,int r)
{
	s[note].left=l;
	s[note].right=r;
	if(l==r)
	{
		int x=l*2-1;
		int y=l*2;
		s[note].res.val[1]=a[x];
		s[note].res.val[2]=a[y];
		memset(s[note].res.cnt,-1,sizeof(s[note].res.cnt));
		s[note].res.cnt[0][0]=0;
		if(a[x]>=a[y]) s[note].res.cnt[2][1]=a[x],s[note].res.cnt[0][2]=a[y];
		if(a[x]<a[y]) s[note].res.cnt[1][2]=a[y],s[note].res.cnt[2][0]=a[x];
		return;
	}
	int mid=(l+r)>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
	push_up(note);
	return;
}
inline void modify(int note,int pos,int x,int y)
{
	if(s[note].left==s[note].right)
	{
		s[note].res.val[x]=y;
		int A=s[note].res.val[1];
		int B=s[note].res.val[2];
		memset(s[note].res.cnt,-1,sizeof(s[note].res.cnt));
		s[note].res.cnt[0][0]=0;
		if(A>=B) s[note].res.cnt[2][1]=A,s[note].res.cnt[0][2]=B;
		if(A<B) s[note].res.cnt[1][2]=B,s[note].res.cnt[2][0]=A;
		return;
	}
	int mid=(s[note].left+s[note].right)>>1;
	if(pos<=mid) modify(lc,pos,x,y);
	else modify(rc,pos,x,y);
	push_up(note);
	return;
}
inline int query()
{
	int ans=1ll<<60;
	for(int i=1;i<=2;++i)
		for(int j=1;j<=2;++j)
			if(s[1].res.cnt[i][j]!=-1) ans=min(ans,s[1].res.cnt[i][j]);
	return ans;
}
signed main()
{
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
	n=read();
	for(int i=1;i<=n;++i) a[i]=read();
	n=(n-1)/2+1;
	build(1,1,n);
	m=read();
	while(m--)
	{
		x=read();
		y=read();
		modify(1,(x-1)/2+1,(x-1)%2+1,y);
		printf("%lld\n",query());
	}
	return 0;
}

CF280D k-Maximum Subsequence Sum

题目
首先有 log 时间复杂度的一个经典问题:区间最大子段和。具体如何实现不说了,见模板题。然后我们就会 k = 1 k=1 k=1 的做法了。考虑我们要解决的问题是选出的 k k k 个子段不交,那么有一种 trick,就是将选出的子段乘 − 1 -1 1,这样有重复选择时会将相交的部分抵消掉。线段是上多维护一点信息。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<map>
#include<queue>
#include<vector>
#include<assert.h>
#define lc note<<1
#define rc note<<1|1
#define mp make_pair
using namespace std;
const int N=1e5+5;
int n,m,opt,pos,val,l,r,k,ans,qwq;
int a[N];
vector<pair<int,int> > vec;
inline int read()
{
	int s=0,t=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
	return s*t;
}
struct Tree
{
	int left,right;
	int tag;
	int sum,ma,mi;
	int lma,lmi,rma,rmi;
	int lpos[7],rpos[7];
	// 0 -> all_max   1 -> all_min
	// 2 -> L_max   3 -> L_min
	// 4 -> R_max   5 -> R_min
}s[N<<2];
inline Tree operator + (const Tree &L,const Tree &R)
{
	Tree res;
	res.left=L.left;res.right=R.right;
	res.tag=1;
	res.sum=L.sum+R.sum;
	res.ma=max(max(L.ma,R.ma),L.rma+R.lma);
	res.mi=min(min(L.mi,R.mi),L.rmi+R.lmi);
	res.lma=max(L.lma,L.sum+R.lma);
	res.lmi=min(L.lmi,L.sum+R.lmi);
	res.rma=max(R.rma,R.sum+L.rma);
	res.rmi=min(R.rmi,R.sum+L.rmi);
	// 0
	if(res.ma==L.ma) res.lpos[0]=L.lpos[0],res.rpos[0]=L.rpos[0];
	if(res.ma==R.ma) res.lpos[0]=R.lpos[0],res.rpos[0]=R.rpos[0];
	if(res.ma==L.rma+R.lma) res.lpos[0]=L.lpos[4],res.rpos[0]=R.rpos[2];
	// 1
	if(res.mi==L.mi) res.lpos[1]=L.lpos[1],res.rpos[1]=L.rpos[1];
	if(res.mi==R.mi) res.lpos[1]=R.lpos[1],res.rpos[1]=R.rpos[1];
	if(res.mi==L.rmi+R.lmi) res.lpos[1]=L.lpos[5],res.rpos[1]=R.rpos[3];
	// 2
	if(res.lma==L.lma) res.lpos[2]=L.lpos[2],res.rpos[2]=L.rpos[2];
	if(res.lma==L.sum+R.lma) res.lpos[2]=L.left,res.rpos[2]=R.rpos[2];
	// 3
	if(res.lmi==L.lmi) res.lpos[3]=L.lpos[3],res.rpos[3]=L.rpos[3];
	if(res.lmi==L.sum+R.lmi) res.lpos[3]=L.left,res.rpos[3]=R.rpos[3];
	// 4
	if(res.rma==R.rma) res.lpos[4]=R.lpos[4],res.rpos[4]=R.rpos[4];
	if(res.rma==R.sum+L.rma) res.lpos[4]=L.lpos[4],res.rpos[4]=R.right;
	// 5
	if(res.rmi==R.rmi) res.lpos[5]=R.lpos[5],res.rpos[5]=R.rpos[5];
	if(res.rmi==R.sum+L.rmi) res.lpos[5]=L.lpos[5],res.rpos[5]=R.right;
	return res;
}
inline void push_up(int note){s[note]=s[lc]+s[rc];}
inline void build(int note,int l,int r)
{
	s[note].left=l;
	s[note].right=r;
	s[note].tag=1;
	if(l==r)
	{
		s[note].sum=s[note].ma=s[note].mi=a[l];
		s[note].lma=s[note].lmi=s[note].rma=s[note].rmi=a[l];
		for(int i=0;i<=5;++i) s[note].lpos[i]=s[note].rpos[i]=l;
		return;
	}
	int mid=(s[note].left+s[note].right)>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
	push_up(note);
	return;
}
inline void calc(Tree &qwq)
{
	Tree tmp=qwq;
	qwq.sum=-tmp.sum;
	qwq.ma=-tmp.mi;qwq.mi=-tmp.ma;
	qwq.lma=-tmp.lmi;qwq.lmi=-tmp.lma;
	qwq.rma=-tmp.rmi;qwq.rmi=-tmp.rma;
	swap(qwq.lpos[0],qwq.lpos[1]);swap(qwq.rpos[0],qwq.rpos[1]);
	swap(qwq.lpos[2],qwq.lpos[3]);swap(qwq.rpos[2],qwq.rpos[3]);
	swap(qwq.lpos[4],qwq.lpos[5]);swap(qwq.rpos[4],qwq.rpos[5]);
	return;
}
inline void push_down(int note)
{
	if(s[note].tag==1) return;
	calc(s[lc]);calc(s[rc]);
	assert(s[lc].tag!=0);
	assert(s[rc].tag!=0);
	s[lc].tag=-s[lc].tag;
	s[rc].tag=-s[rc].tag;
	s[note].tag=1;
	return;
}
inline void modify(int note,int pos,int val)
{
	if(s[note].left==s[note].right)
	{
		s[note].sum=s[note].ma=s[note].mi=val;
		s[note].lma=s[note].lmi=s[note].rma=s[note].rmi=val;
		s[note].tag=1;
		return;
	}
	push_down(note);
	int mid=(s[note].left+s[note].right)>>1;
	if(pos<=mid) modify(lc,pos,val);
	else modify(rc,pos,val);
	push_up(note);
	return;
}
inline void reverse(int note,int l,int r)
{
	if(l<=s[note].left&&s[note].right<=r)
	{
		calc(s[note]);
		assert(s[note].tag!=0);
		s[note].tag=-s[note].tag;
		return;
	}
	push_down(note);
	int mid=(s[note].left+s[note].right)>>1;
	if(l<=mid) reverse(lc,l,r);
	if(r>mid) reverse(rc,l,r);
	push_up(note);
	return;
}
inline Tree query(int note,int l,int r)
{
	if(l<=s[note].left&&s[note].right<=r) return s[note];
	push_down(note);
	int mid=(s[note].left+s[note].right)>>1;
	if(!(r>mid)) return query(lc,l,r);
	else if(!(l<=mid)) return query(rc,l,r);
	return query(lc,l,r)+query(rc,l,r);
}
int main()
{
	n=read();
	for(int i=1;i<=n;++i) a[i]=read();
	build(1,1,n);
	m=read();
	while(m--)
	{
		opt=read();
		if(opt==0)
		{
			pos=read();
			val=read();
			modify(1,pos,val);
		}
		else
		{
			l=read();
			r=read();
			k=read();
			ans=qwq=0;
			while(k--)
			{
				Tree cur=query(1,l,r);
				int lpos=cur.lpos[0],rpos=cur.rpos[0];
				//printf("res:%d lpos:%d rpos:%d\n",cur.ma,lpos,rpos);
				qwq+=cur.ma;
				//printf("res:%d lpos:%d rpos:%d\n",cur.ma,lpos,rpos);
				ans=max(ans,qwq);
				reverse(1,lpos,rpos);
				vec.push_back(mp(lpos,rpos));
			}
			int sz=vec.size();
			for(int i=sz-1;~i;--i)
			{
				int l=vec[i].first,r=vec[i].second;
				reverse(1,l,r);
			}
			vec.clear();
			printf("%d\n",ans);
		}
	}
	return 0;
}

CF1083C Max Mex

题目

具有结合律的信息可以用线段树维护。

因为点权是一个排列,所以一个权值就对应唯一一个点,所以我们可以用线段树维护值域,即维护值域在 [ l , r ] [l,r] [l,r] 范围内的所有点形成的链的两个端点,由于 mex 的性质,允许链上有其他的点。

判断一个点是否在两个点形成的路径上( w w w 是否在 u u u, v v v 的路径上)
lca(pos,w)==pos&&((lca(u,w)==pos&&lca(v,w)==w)||(lca(u,w)==w&&lca(v,w)==pos)) 其中 pos=lca(u,v)

那么我们会如何合并信息了。考虑如何查询答案。线段树上二分即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<map>
#include<queue>
#define lc note<<1
#define rc note<<1|1
#define mp make_pair
using namespace std;
const int N=2e5+5;
int n,q,cnt,opt,u,v,x,y;
int col[N],id[N],head[N],sz[N],father[N],dep[N],son[N],top[N];
inline int read()
{
	int s=0,t=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
	return s*t;
}
struct node
{
	int to,next;
}e[N<<1];
inline void add(int u,int v)
{
	++cnt;
	e[cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt;
	return;
}
inline void dfs1(int u,int fa,int deep)
{
	sz[u]=1;
	father[u]=fa;
	dep[u]=deep;
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs1(v,u,deep+1);
		sz[u]+=sz[v];
		if(sz[v]>sz[son[u]]) son[u]=v;
	}
	return;
}
inline void dfs2(int u,int tp)
{
	top[u]=tp;
	if(!son[u]) return;
	dfs2(son[u],tp);
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		if(v==father[u]||v==son[u]) continue;
		dfs2(v,v);
	}
	return;
}
inline int lca(int u,int v)
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		u=father[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	return u;
}
inline bool check(int u,int v,int w)
{
	int pos=lca(u,v);
	return lca(pos,w)==pos&&((lca(u,w)==pos&&lca(v,w)==w)||(lca(u,w)==w&&lca(v,w)==pos));
}
inline void merge(int &u,int &v,int a,int b,int c,int d)
{
	if(a==0&&b==0){u=c;v=d;return;}
	if(c==0&&d==0){u=a;v=b;return;}
	if(a==-1||b==-1||c==-1||d==-1){u=-1;v=-1;return;}
	int pos[5];
	pos[1]=a;pos[2]=b;pos[3]=c;pos[4]=d;
	for(int i=1;i<=4;++i)
		for(int j=1;j<=4;++j)
		{
			if(i==j) continue;
			a=pos[i];b=pos[j];
			for(int k=1;k<=4;++k)
				if(i!=k&&j!=k) c=pos[k];
			for(int k=4;k;--k)
				if(i!=k&&j!=k) d=pos[k];
			if(check(a,b,c)&&check(a,b,d)){u=a;v=b;return;}
		}
	u=-1;v=-1;
	return;
}
struct Tree
{
	int left,right;
	int u,v;
}s[N<<2];
inline void push_up(int note){merge(s[note].u,s[note].v,s[lc].u,s[lc].v,s[rc].u,s[rc].v);}
inline void build(int note,int l,int r)
{
	s[note].left=l;
	s[note].right=r;
	if(l==r)
	{
		s[note].u=s[note].v=id[l];
		//printf("[%d,%d]  u:%d v:%d\n",l,r,s[note].u,s[note].v);
		return;
	}
	int mid=(l+r)>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
	push_up(note);
	//printf("[%d,%d]  u:%d v:%d\n",l,r,s[note].u,s[note].v);
	return;
}
inline void modify(int note,int pos,int val)
{
	if(s[note].left==s[note].right)
	{
		s[note].u=s[note].v=val;
		return;
	}
	int mid=(s[note].left+s[note].right)>>1;
	if(pos<=mid) modify(lc,pos,val);
	else modify(rc,pos,val);
	push_up(note);
	return;
}
inline int query(int note)
{
	pair<int,int> cur=mp(-1,-1);
	merge(cur.first,cur.second,x,y,s[note].u,s[note].v);
	//printf("query  [%d,%d]  u:%d v:%d\n",s[note].left,s[note].right,cur.first,cur.second);
	if(cur.first!=-1&&cur.second!=-1)
	{
		x=cur.first;y=cur.second;
		return s[note].right;
	}
	if(s[note].left==s[note].right) return s[note].right-1;
	int res=query(lc);
	if(res<s[lc].right) return res;
	return query(rc);
}
int main()
{
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
	n=read();
	for(int i=1;i<=n;++i) col[i]=read()+1,id[col[i]]=i;
	for(int i=2;i<=n;++i)
	{
		int fa=read();
		add(fa,i);
	}
	dfs1(1,0,1);
	dfs2(1,1);
	build(1,1,n);
	q=read();
	while(q--)
	{
		opt=read();
		if(opt==1)
		{
			u=read();
			v=read();
			swap(col[u],col[v]);
			swap(id[col[u]],id[col[v]]);
			modify(1,col[u],id[col[u]]);
			modify(1,col[v],id[col[v]]);
		}
		else x=y=0,printf("%d\n",query(1));
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值