• [做题笔记] lxl 的数据结构选讲(上)


    Julia the snail

    题目描述

    点此看题

    解法

    首先把所有询问离线下来,我们扫描 \(y\),维护所有 \(x\) 的答案,每次需要添加若干个 \(r_i=x\) 的线段。

    \(f(x)\) 表示不经过 \(<x\) 的点,可以到达的最大高度。考虑添加线段 \([l_i,r_i]\) 的效果是:对于 \(i\in[1,l]\),如果 \(f(i)\geq l_i\),那么把 \(f(i)\) 修改成 \(r_i\)

    可以直接上势能线段树维护,对于线段树上的每个区间我们维护 \(mx,cx\),分下面几种情况套路:

    • 如果 \(mx<l_i\),那么整个区间不可能被修改,直接退出。
    • 如果 \(cx<l_i\leq mx\),那么把整个区间为 \(mx\) 的单点修改成 \(r_i\),打标记后退出。
    • 否则递归下去。

    发现如果递归下去,要么是区间没有被整个包含,要么区间值的种类会减少 \(1\),时间复杂度 \(O(n\log n)\)

    #include <cstdio>
    #include <vector>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 100005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,k,ans[M];vector<int> g[M];
    int mx[M<<2],cx[M<<2],fl[M<<2];
    struct node
    {
    	int l,r,id;
    	bool operator < (const node &b) const
    		{return r<b.r;}
    }q[M];
    void add(int i,int c)
    {
    	fl[i]+=c;mx[i]+=c;
    }
    void down(int i)
    {
    	if(!fl[i]) return ;
    	int ml=mx[i<<1],mr=mx[i<<1|1];
    	if(ml>=mr) add(i<<1,fl[i]);
    	if(ml<=mr) add(i<<1|1,fl[i]);
    	fl[i]=0;
    }
    void build(int i,int l,int r)
    {
    	mx[i]=r;
    	if(l==r) return ;
    	int mid=(l+r)>>1;
    	build(i<<1,l,mid);
    	build(i<<1|1,mid+1,r);
    }
    void upd(int i,int l,int r,int L,int R,int x)
    {
    	if(L>r || l>R || mx[i]<R) return ;
    	if(L<=l && r<=R && cx[i]<R)
    		{add(i,x-mx[i]);return ;}
    	int mid=(l+r)>>1;down(i);
    	upd(i<<1,l,mid,L,R,x);
    	upd(i<<1|1,mid+1,r,L,R,x);
    	mx[i]=max(mx[i<<1],mx[i<<1|1]);
    	cx[i]=max(cx[i<<1],cx[i<<1|1]);
    	if(mx[i<<1]!=mx[i]) cx[i]=max(cx[i],mx[i<<1]);
    	if(mx[i<<1|1]!=mx[i]) cx[i]=max(cx[i],mx[i<<1|1]);
    }
    int ask(int i,int l,int r,int x)
    {
    	if(l==r) return mx[i];
    	int mid=(l+r)>>1;down(i);
    	if(x<=mid) return ask(i<<1,l,mid,x);
    	return ask(i<<1|1,mid+1,r,x); 
    }
    signed main()
    {
    	n=read();m=read();
    	for(int i=1;i<=m;i++)
    	{
    		int l=read(),r=read();
    		g[r].push_back(l);
    	}
    	k=read();
    	for(int i=1;i<=k;i++)
    	{
    		int l=read(),r=read();
    		q[i]=node{l,r,i};
    	}
    	sort(q+1,q+1+k);build(1,1,n);
    	for(int i=1,j=1;i<=n;i++)
    	{
    		for(int x:g[i])
    			upd(1,1,n,1,x,i);
    		while(j<=k && q[j].r<=i)
    			ans[q[j].id]=ask(1,1,n,q[j].l),j++;
    	}
    	for(int i=1;i<=k;i++)
    		printf("%d\n",ans[i]);
    }
    

    Nastya and CBS

    题目描述

    点此看题

    解法

    相比于分块做法,我写的做法基本没优势,而且更难写,不过可以练一练经典模型。

    一个常规想法是开一棵线段树,每个节点维护 }]){[( 这种结构,也就是存在一个分界点使得前面只有左括号,后面只有右括号,如果不满足这样的结构那么判定无解。考虑使用哈希在合并两个儿子的时候来匹配括号,可以用线段树套上可持久化 \(\tt treap\),时空复杂度都是 \(O(n\log^2 n)\)

    发现空间复杂度的瓶颈是要维护出所有前缀后缀的哈希值,考虑套用只递归半边的线段树模型,这样我们就只需要维护抵消过后,前面右括号的整体哈希值,后面左括号的整体哈希值。

    考虑怎么快速取出一个前缀或者后缀,下面是取出前缀右括号哈希值的实现方式:

    zxy getl(int i,int k)//节点i,需要取出k的长度
    {
    	if(!k) return zxy(0,0);//出口
    	if(k==vl[i].y) return vl[i];//整个相等,可以直接返回
    	if(k<=vl[ls].y) return getl(ls,k);//右括号全部来源于左半边,单边递归
    	return vl[ls]+(getl(rs,k-vl[ls].y+vr[ls].y)-vr[ls]);
    	//还需要递归右半边,但是这样就需要和左半边的左括号抵消
    	//所以获取的长度要改变,并且结果需要减去左半边的左括号
    }
    

    把哈希函数封装就可以方便的实现,注意右括号的合并顺序应该是从右到左(回文的顺序)

    询问的时候,我们先把 \(O(\log n)\) 个区间取出来,然后用栈的方式合并,时间复杂度 \(O(n\log ^2n)\),空间复杂度 \(O(n)\)

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int M = 100005;
    const int MOD = 1e9+7;
    #define ll long long
    #define ls (i<<1)
    #define rs (i<<1|1)
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,w[M],iw[M],fl[M<<2];
    struct zxy
    {
    	int x,y;
    	zxy(int X=0,int Y=0) : x(X) , y(Y) {}
    	friend bool operator == (zxy a,zxy b)
    		{return a.x==b.x && a.y==b.y;}
    	friend zxy operator + (zxy a,zxy b)
    		{return zxy((a.x+(ll)b.x*w[a.y])%MOD,a.y+b.y);}
    	friend zxy operator - (zxy a,zxy b)
    		{return zxy((ll)(a.x-b.x+MOD)*iw[b.y]%MOD,a.y-b.y);}
    }vl[M<<2],vr[M<<2];
    ll qkpow(ll a,ll b)
    {
    	ll r=1;
    	while(b>0)
    	{
    		if(b&1) r=r*a%MOD;
    		a=a*a%MOD;
    		b>>=1;
    	}
    	return r;
    }
    zxy getl(int i,int k)
    {
    	if(!k) return zxy(0,0);
    	if(k==vl[i].y) return vl[i];
    	if(k<=vl[ls].y) return getl(ls,k);
    	return vl[ls]+(getl(rs,k-vl[ls].y+vr[ls].y)-vr[ls]);
    }
    zxy getr(int i,int k)
    {
    	if(!k) return zxy(0,0);
    	if(k==vr[i].y) return vr[i];
    	if(k<=vr[rs].y) return getr(rs,k);
    	return vr[rs]+(getr(ls,k-vr[rs].y+vl[rs].y)-vl[rs]);
    }
    void up(int i)
    {
    	if(fl[ls] || fl[rs]) {fl[i]=1;return ;}
    	fl[i]=0;vl[i]=vl[ls];vr[i]=vr[rs];
    	if(vr[ls].y<=vl[rs].y)
    	{
    		if(vr[ls]==getl(rs,vr[ls].y))
    			vl[i]=vl[i]+(vl[rs]-vr[ls]);
    		else fl[i]=1;
    	}
    	else
    	{
    		if(vl[rs]==getr(ls,vl[rs].y))
    			vr[i]=vr[i]+(vr[ls]-vl[rs]);
    		else fl[i]=1;
    	}
    }
    void ins(int i,int l,int r,int x,int y)
    {
    	if(l==r)
    	{
    		if(y>0) vl[i]=zxy(0,0),vr[i]=zxy(y,1);
    		if(y<0) vl[i]=zxy(-y,1),vr[i]=zxy(0,0);
    		return ;
    	}
    	int mid=(l+r)>>1;
    	if(mid>=x) ins(ls,l,mid,x,y);
    	else ins(rs,mid+1,r,x,y);
    	up(i);
    }
    int k,s[50];zxy h[50];
    void get(int i,int l,int r,int L,int R)
    {
    	if(L>r || l>R) return ;
    	if(L<=l && r<=R) {s[++k]=i;return ;}
    	int mid=(l+r)>>1;
    	get(ls,l,mid,L,R);
    	get(rs,mid+1,r,L,R);
    }
    zxy work(int i,int k)
    {
    	if(!k) return zxy(0,0);
    	if(k==h[i].y) return h[i];
    	if(k<=vr[s[i]].y) return getr(s[i],k);
    	return vr[s[i]]+(work(i-1,k-vr[s[i]].y+vl[s[i]].y)-vl[s[i]]);
    }
    int ask(int l,int r)
    {
    	k=0;get(1,1,n,l,r);
    	for(int i=1;i<=k;i++)
    	{
    		if(fl[s[i]]) return 0;
    		if(h[i-1].y<vl[s[i]].y) return 0;
    		if(vl[s[i]]==work(i-1,vl[s[i]].y))
    			h[i]=vr[s[i]]+(h[i-1]-vl[s[i]]);
    		else return 0;
    	}
    	return h[k].y==0;
    }
    signed main()
    {
    	n=read();read();
    	w[0]=iw[0]=1;w[1]=371;iw[1]=qkpow(371,MOD-2);
    	for(int i=2;i<=n;i++) w[i]=(ll)w[i-1]*w[1]%MOD;
    	for(int i=2;i<=n;i++) iw[i]=(ll)iw[i-1]*iw[1]%MOD;
    	for(int i=1;i<=n;i++) ins(1,1,n,i,read());
    	m=read();
    	while(m--)
    	{
    		int op=read(),x=read(),y=read();
    		if(op==1) ins(1,1,n,x,y);
    		else puts(ask(x,y)?"Yes":"No");
    	}
    }
    

    Path

    题目描述

    点此看题

    解法

    \(d(x)\) 表示 \(x\) 到根边权的异或和,那么 \(f(x,y)\) 可以表示成 \(d(x)\oplus d(y)\),这种只和两个单点有关的形式很舒服。

    使用枚举法转化问题,由于限制是点不交,考虑对于每个点 \(u\) 分别求出,从子树内选出两个点(包含 \(u\))的最大异或和;从子树外选出两个点(不包含 \(u\))的最大异或和;把这两个东西加起来求个最大值就是答案。

    第一个问题是比较常规的,考虑树上启发式合并,再拿棵 \(\tt trie\) 树支持最大值查询即可,时间复杂度 \(O(n\log^2 n)\)

    第二个问题也很好做,可以考虑求出全局的最优点对 \(d(p)\oplus d(q)\),这样如果一个点不在 \(p,q\) 到根的链上,最大异或和就是 \(d(p)\oplus a(q)\),那么现在我们只需要对于 \(p,q\) 这两条链求一遍。

    直接按 \(\tt dfn\) 序扫链,由于只会进入其中一个子树,直接暴力插入其他子树即可,时间复杂度 \(O(n\log n)\)

    总时间复杂度 \(O(n\log ^2 n)\)

    总结

    对于限制较弱的最值问题(比如树上求子树外的最值),可以考虑先求出全局最值点,然后收紧限制,这样可以将要考虑的范围缩小(比如本题最后就只需要计算两条链的情况)

    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <iostream>
    using namespace std;
    const int M = 30005;
    const int N = 128*M;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,tot,f[M],d[M],id[M],fa[M],in[M],out[M];
    int cnt,rt[M],ch[N][2],t[N],fl[M],b[M];
    struct edge{int v,c,next;}e[M<<1];vector<int> v[M];
    void clr() {memset(t,0,sizeof t);}
    void ins(int &x,int y)
    {
    	if(!x) x=++cnt;t[x]++;
    	for(int i=30,p=x;i>=0;i--)
    	{
    		int d=y>>i&1;
    		if(!ch[p][d]) ch[p][d]=++cnt;
    		t[p=ch[p][d]]++;
    	}
    }
    int ask(int x,int y)
    {
    	int r=0;
    	for(int i=30;i>=0;i--)
    	{
    		int d=y>>i&1;
    		if(t[ch[x][d^1]]) x=ch[x][d^1],r|=(1<<i);
    		else x=ch[x][d];
    	}
    	return r;
    }
    void mg(int x,int u,int z,int i)
    {
    	if(!x) return ;
    	if(i==-1)
    	{
    		in[u]=max(in[u],ask(rt[u],z));
    		ins(rt[u],z);return ;
    	}
    	mg(ch[x][0],u,z,i-1);
    	mg(ch[x][1],u,z|(1<<i),i-1);
    }
    void dfs(int u,int p)
    {
    	id[++m]=u;fa[u]=p;ins(rt[u],d[u]);
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v,c=e[i].c;
    		if(v==p) continue;
    		d[v]=d[u]^c;dfs(v,u);
    		if(t[rt[u]]<t[rt[v]]) swap(rt[u],rt[v]);
    		mg(rt[v],u,0,30);in[u]=max(in[u],in[v]);
    	}
    }
    void work(int u)
    {
    	for(int x=u;x;x=fa[x]) fl[x]=1;
    	clr();int mx=0;
    	for(int k=1;k<=m;k++)
    	{
    		int i=id[k];v[i].clear();
    		b[i]=fl[i]?i:b[fa[i]];
    		v[b[i]].push_back(d[i]);
    	}
    	for(int k=1;k<=m;k++)
    	{
    		int i=id[k];
    		if(!fl[i]) continue;
    		out[i]=max(out[i],mx);
    		for(int x:v[i])	
    			mx=max(mx,ask(rt[0],x)),ins(rt[0],x);
    		fl[i]=0;v[i].clear();
    	}
    }
    signed main()
    {
    	n=read();
    	for(int i=1;i<n;i++)
    	{
    		int u=read(),v=read(),c=read();
    		e[++tot]=edge{v,c,f[u]},f[u]=tot;
    		e[++tot]=edge{u,c,f[v]},f[v]=tot;
    	}
    	dfs(1,0);clr();
    	int A=0,B=0,p=0,q=0,ans=0;
    	for(int i=1;i<=n;i++)
    	{
    		int v=ask(rt[0],d[i]);ins(rt[0],d[i]);
    		if((A^B)<v) A=d[i],B=v^d[i],p=i;
    	}
    	clr();memset(out,-1,sizeof out);
    	for(int i=1;i<=n;i++) if(B==d[i]) q=i;
    	work(p);work(q);
    	for(int i=1;i<=n;i++)
    		if(out[i]==-1) out[i]=A^B;
    	for(int i=2;i<=n;i++)
    		ans=max(ans,in[i]+out[i]);
    	printf("%d\n",ans);
    }
    

    Treequery

    题目描述

    点此看题

    解法

    注意题目是求公共部分(我一开始读成了求并),我们按照 \(u\)\([l,r]\) 中点的位置关系来分类讨论:

    • 如果 \([l,r]\) 中的点全部处于 \(u\) 的子树内,我们找到 \([l,r]\)\(\tt dfs\) 序最小和最大的点,求出它们的 \(\tt lca\) 记为 \(x\),根据虚树的有关理论,\((u,x)\) 的距离就是答案。
    • 如果 \([l,r]\) 中的点分居 \(u\) 的子树内和子树外,答案为 \(0\)
    • 如果 \([l,r]\) 中的点都处于 \(u\) 的子树外,那么 \(u\)\([l,r]\) 的虚树的最短距离就是答案。

    第三种情况有点难做,我们不妨再对虚树和 \(u\) 的位置关系进行分类讨论:

    • 如果 \(u\) 不在虚树根的子树内,那么答案是 \(u\) 和虚树根的距离。
    • 否则尝试把 \(u\) 插入虚树,即找到 \(\tt dfs\) 序的前驱后继,和 \(u\) 可以求出两个 \(\tt lca\),记为 \(x_1,x_2\),那么 \(u\)\(x_1,x_2\) 距离的最小值就是答案。

    在主席树上二分就可以查询 \(\tt dfs\) 序的前驱后继,时间复杂度 \(O(n\log n)\)

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int M = 200005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,k,tot,f[M],fa[M][20],in[M],out[M],dep[M],id[M];
    int cnt,dis[M],rt[M],s[20*M],ls[20*M],rs[20*M];
    struct edge{int v,c,next;}e[M<<1];
    void dfs(int u,int p)
    {
    	fa[u][0]=p;dep[u]=dep[p]+1;
    	id[in[u]=++cnt]=u;
    	for(int i=1;i<20;i++)
    		fa[u][i]=fa[fa[u][i-1]][i-1];
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v,c=e[i].c;
    		if(v==p) continue;
    		dis[v]=dis[u]+c;
    		dfs(v,u);
    	}
    	out[u]=cnt;
    }
    int lca(int u,int v)
    {
    	if(dep[u]<=dep[v]) swap(u,v);
    	for(int i=19;i>=0;i--)
    		if(dep[fa[u][i]]>=dep[v])
    			u=fa[u][i];
    	if(u==v) return u;
    	for(int i=19;i>=0;i--)
    		if(fa[u][i]^fa[v][i])
    			u=fa[u][i],v=fa[v][i];
    	return fa[u][0];
    }
    void ins(int &x,int y,int l,int r,int p)
    {
    	x=++cnt;s[x]=s[y]+1;
    	ls[x]=ls[y];rs[x]=rs[y];
    	if(l==r) return ;
    	int mid=(l+r)>>1;
    	if(mid>=p) ins(ls[x],ls[y],l,mid,p);
    	else ins(rs[x],rs[y],mid+1,r,p);
    }
    int ask(int x,int y,int l,int r,int L,int R)
    {
    	if(L>r || l>R) return 0;
    	if(L<=l && r<=R) return s[x]-s[y]>0;
    	int mid=(l+r)>>1;
    	return ask(ls[x],ls[y],l,mid,L,R)|
    	ask(rs[x],rs[y],mid+1,r,L,R);
    }
    int getp(int x,int y,int l,int r,int p)
    {
    	if(l>p || s[x]-s[y]==0) return 0;
    	if(l==r) return id[l];
    	int mid=(l+r)>>1,t=0;
    	if(t=getp(rs[x],rs[y],mid+1,r,p)) return t;
    	return getp(ls[x],ls[y],l,mid,p);
    }
    int gets(int x,int y,int l,int r,int p)
    {
    	if(r<p || s[x]-s[y]==0) return 0;
    	if(l==r) return id[l];
    	int mid=(l+r)>>1,t=0;
    	if(t=gets(ls[x],ls[y],l,mid,p)) return t;
    	return gets(rs[x],rs[y],mid+1,r,p);
    }
    signed main()
    {
    	n=read();m=read();
    	for(int i=1;i<n;i++)
    	{
    		int u=read(),v=read(),c=read();
    		e[++tot]=edge{v,c,f[u]},f[u]=tot;
    		e[++tot]=edge{u,c,f[v]},f[v]=tot;
    	}
    	dfs(1,0);
    	for(int i=1;i<=n;i++)
    		ins(rt[i],rt[i-1],1,n,in[i]);
    	for(int i=1,ans=0;i<=m;i++)
    	{
    		int u=read(),l=read(),r=read();
    		u^=ans;l^=ans;r^=ans;l--;
    		int A=ask(rt[r],rt[l],1,n,1,in[u]-1);
    		int B=ask(rt[r],rt[l],1,n,in[u],out[u]);
    		int C=ask(rt[r],rt[l],1,n,out[u]+1,n);
    		if(B&(A|C)) {printf("%d\n",ans=0);continue;}
    		if(A+C==0)
    		{
    			int v1=getp(rt[r],rt[l],1,n,out[u]);
    			int v2=gets(rt[r],rt[l],1,n,in[u]);
    			ans=dis[lca(v1,v2)]-dis[u];
    			printf("%d\n",ans);continue;
    		}
    		int v1=gets(rt[r],rt[l],1,n,1);
    		int v2=getp(rt[r],rt[l],1,n,n);
    		int x=lca(v1,v2);
    		if(!(in[x]<=in[u] && in[u]<=out[x]))
    		{
    			ans=dis[x]+dis[u]-2*dis[lca(x,u)];
    			printf("%d\n",ans);continue;
    		}
    		ans=0x3f3f3f3f;
    		if(A)
    		{
    			int v=getp(rt[r],rt[l],1,n,in[u]-1);
    			ans=min(ans,dis[u]-dis[lca(u,v)]);
    		}
    		if(C)
    		{
    			int v=gets(rt[r],rt[l],1,n,out[u]+1);
    			ans=min(ans,dis[u]-dis[lca(u,v)]);
    		}
    		printf("%d\n",ans);
    	}
    }
    
  • 相关阅读:
    『Argparse』命令行解析
    『MXNet』专题汇总
    用.NET开发通用Windows App
    ASP.NET 5探险(6):升级ASP.NET 5到beta6
    使用ASP.NET MVC、Rabbit WeixinSDK和Azure快速开发部署微信后台
    Visual Studio 2015将在7月20号RTM
    VS2015上又一必备免费插件:Refactoring Essentials
    ASP.NET 5探险(5):利用AzureAD实现单点登录
    Visual Studio Code升级到0.5,提供对ES6的更好支持
    ASP.NET 5探险(4):如何把ASP.NET 5从beta4升级到beta5
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16416921.html
Copyright © 2020-2023  润新知