可持久化?

P3919 【模板】可持久化线段树 1(可持久化数组) 用于维护一个区间问题,具体有历史版本查值,区间第k小等等。具体而言,是对于一个点新开一个区间维护,这样既节省空间,又不破坏树,但除了这一个点,其他点可以沿用之前的,因为它们是不变的。比如这个图:
在这里插入图片描述就是如此,不变的点是不用修改的,直接链上去就好了,注意一下根是要单独拿一个数组保存的。

#include<bits/stdc++.h>
using namespace std;
int n,m,len=0;
int root[32*1000001];
int a[1000001]; 
struct node
{
	int l,r,lc,rc,sum;
};node e[32*1000001];
int bt(int x,int y)
{
	int now=++len;
	int l=x,r=y,lc=-1,rc=-1,sum=0;
	if(x==y) sum=a[x];
	else 
	{
		int mid=(l+r)/2;
		lc=bt(l,mid);rc=bt(mid+1,r);
	}
	e[now]={l,r,lc,rc,sum};
	return now;
}
int cg(int now,int x,int k)
{
	int l=e[now].l,r=e[now].r,lc=e[now].lc,rc=e[now].rc,sum=e[now].sum;
	int noew=++len;e[noew]={l,r,lc,rc,sum};//新开一个节点保存一下 
	if(l==r) e[noew].sum=k;
	else 
	{
		int mid=(l+r)/2;
		if(x>=mid+1) e[noew].rc=cg(rc,x,k);
		else e[noew].lc=cg(lc,x,k);
	}
	return noew;
}
int find(int now,int x)
{
	int l=e[now].l,r=e[now].r;
	if(l==r) return e[now].sum;
	else 
	{
		int mid=(l+r)/2,lc=e[now].lc,rc=e[now].rc;
		if(x<=mid) return find(lc,x);
		else return find(rc,x);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	root[0]=bt(1,n);
	for(int i=1;i<=m;i++)
	{
		int rt,op;scanf("%d%d",&rt,&op);
		if(op==1)
		{
			int x,k;scanf("%d%d",&x,&k);
			root[i]=cg(root[rt],x,k);
		}
		else 
		{
			int x;scanf("%d",&x);root[i]=root[rt];
			printf("%d\n",find(root[rt],x));
		}
	}
	return 0;
}

下一个板子:P3834 【模板】可持久化线段树 2同样经典,同样好玩。这一题中,主席树维护的是区间中排序后,维护的是1到i中,每个大小的数的数量有多少:就是这个插入,但是1到i这些数有n个,所以要拿可持久话线段树维护。然后查询的时候,比较左右区间的数的数量,比如左区间如果>k那么去左区间嘛差不多就这样。

	sort(a+1,a+n+1);//先对插入的数排序
	int siz=unique(a+1,a+1+n)-a-1; //去重
	len=0,root[0]=bt(1,siz);//建树
	for(int i=1;i<=n;i++)
	{
		int x=lower_bound(a+1,a+1+siz,b[i])-a;
		root[i]=cg(root[i-1],x);	
	}

噢噢噢注意两个点,第一个是这题用了前缀和的思想维护的是1到i的嘛,所以查询的时候要减去1到l-1位的,第二个是线段树维护的是离散值,所以返回的是后如果你想简单点就直接 a [ l ] 具体怎么写放下面:(呃啊心跳回忆真好听)

#include<bits/stdc++.h>
using namespace std;
int n,m;
int len=0,tot=0,root[210001];
int b[210001],a[210001];
struct node
{
	int l,r,lc,rc,sum;
};node e[32*210001];
bool cmp(const int &x,const int &y)
{
	return x<y;
}
int bt(int x,int y)
{
	int now=++len;
	int l=x,r=y,lc=-1,rc=-1;
	if(x==y) ;
	else 
	{
		int mid=(l+r)/2;
		lc=bt(l,mid);rc=bt(mid+1,r);
	}
	e[now]={l,r,lc,rc,0};
	return now;
}
int add(int now,int x,int k)
{
	int noew=++len;
	int l=e[now].l,r=e[now].r,lc=e[now].lc,rc=e[now].rc,sum=e[now].sum+1;
	if(l==r) ;
	else 
	{
		int mid=(l+r)/2;
		if(x>=mid+1) rc=add(rc,x,k);
		else lc=add(lc,x,k);
	}	
	e[noew]={l,r,lc,rc,sum};
	return noew;
}
int find(int x,int y,int k)
{
	int l=e[x].l,r=e[x].r;
	if(l==r) return a[l];
	else 
	{
		int num=e[e[y].lc].sum-e[e[x].lc].sum;
		if(num>=k) return find(e[x].lc,e[y].lc,k);
		else return find(e[x].rc,e[y].rc,k-num);
	}
}
int main() 
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
	sort(a+1,a+n+1,cmp);//先对插入的数排序
	int siz=unique(a+1,a+1+n)-a-1; //去重
	len=0,root[0]=bt(1,siz);//建树
	for(int i=1;i<=n;i++)
	{
		int x=lower_bound(a+1,a+siz+1,b[i])-a;
		root[i]=add(root[i-1],x,1);
	}
	while(m--)
	{
		int x,y,k;scanf("%d%d%d",&x,&y,&k);
		printf("%d\n",find(root[x-1],root[y],k));
	}
	return 0;
}

下一个P3567 [POI2014]KUR-Couriers呃这个真的离谱,空间卡的巨离谱草,我受不了,我改了半天还是没过,只好换了一个种写法:巨tm离谱受不了

#include<bits/stdc++.h>
using namespace std;
int n,m,len=0;
int a[520001],b[520001];
struct node
{
	int lc,rc,sum;	
};node e[500001<<5];
int root[520001];
int bt(int x,int y)
{
	int now=++len;
	int lc=-1,rc=-1;
	if(x==y) ;
	else
	{
		int mid=(x+y)/2;
		lc=bt(x,mid);rc=bt(mid+1,y);
	}
	e[now]={lc,rc,0};
	return now;
}
int add(int now,int x,int k,int l,int r)
{
	int noew=++len;
	int lc=e[now].lc,rc=e[now].rc,sum=e[now].sum+k;
	if(l==r) ;
	else
	{
		int mid=(l+r)/2;
		if(x>=mid+1) rc=add(rc,x,k,mid+1,r);
		else lc=add(lc,x,k,l,mid);
	}
	e[noew]={lc,rc,sum};
	return noew;
}
int find(int x,int y,int k,int l,int r)
{
	if(l>=r) return a[l];
	else 
	{
		int mid=(l+r)/2;
		int numl=e[e[y].lc].sum-e[e[x].lc].sum,numr=e[e[y].rc].sum-e[e[x].rc].sum;
		if(numl>k) return find(e[x].lc,e[y].lc,k,l,mid);
		if(numr>k) return find(e[x].rc,e[y].rc,k,mid+1,r);
		return 0;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
	sort(a+1,a+n+1);int siz=unique(a+1,a+n+1)-a-1;
	root[0]=bt(1,siz);
	for(int i=1;i<=n;i++) 
	{
		int x=lower_bound(a+1,a+siz+1,b[i])-a;
		root[i]=add(root[i-1],x,1,1,siz);
	}
	while(m--)
	{
		int x,y;scanf("%d%d",&x,&y);
		printf("%d\n",find(root[x-1],root[y],(y-x+1)/2,1,siz));
	}
	return 0;
}

冲?P2617 Dynamic Rankings呃看起来是前面那道题的加强版,加了一个什么呢加了一个改值操作。那么我们用上面的维护方式肯定是不行了,因为每次都要做O(NlogN)这样就爆炸了,那咋办,那就用树状数组的方式维护呗,那就改成logN^2了,挺好的。好好想想对不对嘛。呃啊打了一会发现要动态开点不然不大好:

//其实就是,将O(nlogn)+O(1)的修改加查询,均摊为O(logn)的操作
//呃感觉好多题都有这样的操作,以空间和时间的均摊对吧,就像之前有一题不过我忘了。 
//至于这一题为什么离线做,当然是因为离散化呀。 
#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
//#define int long long 
using namespace std;
int n,m,len=0,tot=0,a[100007],b[4*100007];
int n1,n2,t1[100007],t2[100007],root[100007];
struct node
{
	int lc,rc,sum;
};node e[400*100007];
int c[100007],d[100007],kk[100007];
bool cmp(const int &x,const int &y)
{
	return x<y;
}
void add(int &now,int l,int r,int x,int k)
{
	if(!now) now=++len;
	e[now].sum+=k;
	if(l==r) return ;
	int mid=(l+r)/2;
	if(x<=mid) add(e[now].lc,l,mid,x,k);
	else add(e[now].rc,mid+1,r,x,k);
}	
void cg(int id,int val)
{
	int x=lower_bound(b+1,b+tot+1,a[id])-b;
	for(int i=id;i<=n;i+=lowbit(i)) add(root[i],1,tot,x,val);
	return ;	
}
int find(int l,int r,int k)
{
	if(l==r) return l;
	int mid=(l+r)/2,sum=0;
	for(int i=1;i<=n2;i++) sum+=e[e[t2[i]].lc].sum;
	for(int i=1;i<=n1;i++) sum-=e[e[t1[i]].lc].sum;
	if(sum>=k)
	{
		for(int i=1;i<=n1;i++) t1[i]=e[t1[i]].lc;
		for(int i=1;i<=n2;i++) t2[i]=e[t2[i]].lc;
		return find(l,mid,k);
	}
	else 
	{
		for(int i=1;i<=n1;i++) t1[i]=e[t1[i]].rc;
		for(int i=1;i<=n2;i++) t2[i]=e[t2[i]].rc;
		return find(mid+1,r,k-sum);
	}
}
int getnum(int x,int y,int val)
{
	n1=n2=0;
	for(int i=x-1;i>=1;i-=lowbit(i)) t1[++n1]=root[i];
	for(int i=y;i>=1;i-=lowbit(i)) t2[++n2]=root[i];
	return find(1,tot,val);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[++tot]=a[i];
	for(int i=1;i<=m;i++)
	{
		char p[3];scanf("%s",p+1);
		if(p[1]=='Q') scanf("%d%d%d",&c[i],&d[i],&kk[i]);	
		else scanf("%d%d",&c[i],&d[i]),b[++tot]=d[i];
	}
	sort(b+1,b+tot+1,cmp);tot=unique(b+1,b+tot+1)-b-1;
	for(int i=1;i<=n;i++) cg(i,1);
	for(int i=1;i<=m;i++)
	{
		if(kk[i]) printf("%d\n",b[getnum(c[i],d[i],kk[i])]);
		else cg(c[i],-1),a[c[i]]=d[i],cg(c[i],1);
	}
	return 0;
}

差不多就是这样,其实和静态的不同在于它的点没有保存一些值,而是随时准备更改。再写两题就好P3963 [TJOI2013] 奖学金呃发现用优先队列写会好些,所以P3939 数颜色这一题的话对每一个颜色动态开一颗线段树,然后直接维护即可。这个倒是新鲜罢,并也不是太过高深的东西。

#include<bits/stdc++.h>
using namespace std;
int n,m;
int len=0,cl[310001],root[310001];
struct node
{
	int lc,rc,sum;
};node e[30*310001];
void add(int &now,int l,int r,int x,int c)
{
	if(!now) now=++len;e[now].sum+=c;
	if(l==r) return ;
	int mid=(l+r)/2;
	if(x<=mid) add(e[now].lc,l,mid,x,c);
	else add(e[now].rc,mid+1,r,x,c);
	return ;
}
int find(int now,int l,int r,int x,int y)
{
	if(!now) return 0;
	if(l==x&&y==r) return e[now].sum;
	int mid=(l+r)/2,lc=e[now].lc,rc=e[now].rc;
	if(x>=mid+1) return find(rc,mid+1,r,x,y);
	else if(y<=mid) return find(lc,l,mid,x,y);
	else return find(lc,l,mid,x,mid)+find(rc,mid+1,r,mid+1,y);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&cl[i]),add(root[cl[i]],1,n,i,1);
	while(m--)
	{
		int op;scanf("%d",&op);
		if(op==1) 
		{
			int x,y,c;scanf("%d%d%d",&x,&y,&c);
			printf("%d\n",find(root[c],1,n,x,y));
		}
		else 
		{
			int x;scanf("%d",&x);
			add(root[cl[x]],1,n,x,-1);
			add(root[cl[x+1]],1,n,x+1,-1);
			add(root[cl[x]],1,n,x+1,1);
			add(root[cl[x+1]],1,n,x,1);
            swap(cl[x],cl[x+1]);
		}
	}
	return 0;
}

呃啊另一道板子:P3168 [CQOI2015]任务查询系统区间修改,点查询,采用差分的思想,就维护区间首尾然后再利用主席树求出前缀和,虽然还是可以用树状数组优化一下的说。最后在树上二分一下即可。。。具体说就是以时间戳来算“从第 s [ i ] 秒开始,在第 e [ i ]秒后结束”用这个,然后将k放入树中对吧就这样了:

//因为是差分,所以修改是这样的 
#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,maxx,tot=0,len=0;
int root[200005];
int to[200005],he[200005],nxt[200005],_to[200005],_he[200005],_nxt[200005];
struct node
{
	int lc,rc,cnt,sum; 
};node e[24000001];
inline void add(int u,int v) {
	to[++tot]=v,nxt[tot]=he[u],he[u]=tot;
}
inline void edd(int u,int v) {
	_to[++tot]=v,_nxt[tot]=_he[u],_he[u]=tot;
}
void add(int &now,int l,int r,int x,int k)
{
	int noew=++len;
	e[noew].lc=e[now].lc,e[noew].rc=e[now].rc;
	e[noew].sum=e[now].sum+k*x;e[noew].cnt=e[now].cnt+k;now=noew;
	if(l==r) return ;
	int mid=(l+r)/2;
	if(x<=mid) add(e[now].lc,l,mid,x,k);
	else add(e[now].rc,mid+1,r,x,k);
} 
int getsum(int now,int l,int r,int k)
{
	if(l==r) return min(e[now].sum,l*k);
	else 
	{
		int mid=(l+r)/2,cnt=e[e[now].lc].cnt;
		if(cnt>=k) return getsum(e[now].lc,l,mid,k);
		else return getsum(e[now].rc,mid+1,r,k-cnt)+e[e[now].lc].sum;
	}
}
signed main()
{
	scanf("%lld%lld",&m,&n);
	for(int i=1;i<=m;i++) 
	{
		int x,y,k;scanf("%lld%lld%lld",&x,&y,&k);
		add(x,k),edd(y+1,k);maxx=max(maxx,k);
	}
	for(int i=1;i<=n;i++)
	{
		root[i]=root[i-1];
		for(int y=he[i];y;y=nxt[y]) add(root[i],1,maxx,to[y],1);
		for(int y=_he[i];y;y=_nxt[y]) add(root[i],1,maxx,_to[y],-1);
	}
	int ans=1;
	for(int i=1;i<=n;i++) 
	{
		int x,a,b,c,k;scanf("%lld%lld%lld%lld",&x,&a,&b,&c);
		k=(a*ans+b)%c+1;printf("%lld\n",ans=getsum(root[x],1,maxx,k));
	}
	return 0;
} 

P1383 高级打字机复习一手:

//动态处理法!动态建点,然后比较左右子树的大小 
#include<bits/stdc++.h> 
using namespace std;
int n,m,len=0,tot=0,root[200000];
struct node
{
	int l,r,lc,rc,siz;char k; 
};node e[8000001];
int bt(int x,int y)
{
	int now=++len,l=x,r=y,lc=-1,rc=-1;
	if(l==r) ;
	else 
	{
		int mid=(l+r)/2;
		lc=bt(l,mid);rc=bt(mid+1,r);
	}
	e[now]={l,r,lc,rc,0,1};
	return now;	
}
int cg(int now,char k)
{
	int noew=++len,l=e[now].l,r=e[now].r;e[noew]=e[now];
	if(l==r) e[noew].siz+=1,e[noew].k=k;
	else
	{
		int lc=e[now].lc,rc=e[now].rc;
		if(e[lc].siz>=e[lc].r-e[lc].l+1) e[noew].rc=cg(rc,k);
		else e[noew].lc=cg(lc,k);
		e[noew].siz=e[e[noew].lc].siz+e[e[noew].rc].siz;
	}
	return noew;
}
char find(int now,int x)
{
	int l=e[now].l,r=e[now].r;
	if(l==r) return e[now].k;
	else 
	{
		int lc=e[now].lc,rc=e[now].rc;
		if(e[lc].siz>=x) return find(lc,x);
		else return find(rc,x-e[lc].siz);
	}	
}
int main()
{
	scanf("%d",&n);root[tot]=bt(1,n);
	while(n--)
	{
		char s;cin>>s;
		if(s=='U') 
		{
			int x;scanf("%d",&x);tot++;root[tot]=root[tot-x-1];
		}
		else if(s=='Q') 
		{
			int x;scanf("%d",&x);
			printf("%c\n",find(root[tot],x));
		}
		else 
		{
			char x;cin>>x;
			tot++;root[tot]=cg(root[tot-1],x);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值