• loj3066


    贡献一波继 chasedeath 和 tzc_wk 的全网第三篇题解!

    笑死,今天 mns 完完全全搬了 NOIP2020 前一场 mns 的四道题。虽然当时我没打,但是还是补了前三题。当时觉得 T4 是个毒瘤题(其实 qs)不会做,现在来补一波。


    参考「God 的高妙算法」,给出了 (mathrm O(1))​ 求链交(假装 lca 用 st 表)的方法。方法分出了两类:(a_1 eq a_2)(a_1=a_2),其中 (a_{1,2})​ 分别是两条链的 lca。那我们也分成这两类分别求答案,最后 max 起来。

    至于要输出方案,我的方法记录对应路径的编号就太麻烦了(因为某棵线段树合并里涉及到删除操作)。我的方案是记录最终的交的两端(这个简单,下面就不讲这个的做法了),然后找出所有包含这个交的路径,随便选两个就可以了。判包含的话,直接判是否两端都包含;而判一个点是否包含于一条链的话,这等价于 (mathrm{dis}(x,a)+mathrm{dis}(y,a)=mathrm{dis}(x,y))

    第一类:(a_1 eq a_2)

    较为简单的一类。我们设 (a_2in a_1 o x_1),那么交显然是 (a_1 o x_1) 上的一段直链。于是我们就可以将 (x_1 o y_1) 这条弯链拆成 (a_1 o x_1,a_1 o y_1) 两条直链。以及 (x_2 o y_2) 其实也可以拆,因为 (a_2 o x_2,a_2 o y_2) 中显然至多有一条跟 (x_1 o y_1) 有交边。

    现在问题就转化成了一堆直链的问题。两条直链被 count 当且仅当其中一条的上端点在另一条上面。考虑枚举上端点高的那条,然后计算所有上端点在其内部的链对它的贡献?发现这样不太好做。因为这条交有可能是它的任意一个子段,而我们发现如果枚举矮的那条,那么交必定是它的一段前缀。这就很好做了:考虑二分出这个前缀,chk 怎么 chk 呢,那显然就是看包含上端点的所有路径的下端点有没有在该前缀下端点的子树内部的。

    这显然可以差分:将每条链差分,表示上端点位于这条链上的点都要以其为包含它的链。那就打 adddel 标记,dfs 的时候要做的是添加 / 删除下端点,合并儿子,然后子树(区间)查询是否有点(其实就是计数)(todo-list 为上端点为当前点的链们)。这玩意要维护线段树,显然可线段树合并(其实也可以 dsu + BIT 小常数 2log,还好写。但我就是写线段树合并,唉,就是玩!)。虽然线段树合并是 1log,但是二分 + 区间查询是 2log。。。。。二分只能用树上倍增实现,这里还有一个问题:树上倍增只支持往上倍增,但我们查最长前缀显然是往下,怎么办呢?只需要改为对「最上面的不能作为前缀的点」进行倍增即可。注意:计数的话,由于自身被算了一遍,所以 chk 其实是 cnt()==1

    第二类:(a_1=a_2)

    这类比较难。。。首先把 lca 相同的用桶预处理出来好吧,然后考虑对 (a) 的桶计算答案。

    交其实就是 (mathrm{lca}(x_1,x_2) omathrm{lca}(y_1,y_2))​,但也不完全准确,因为有可能会将 (x_2,y_2)​ 交换。但你会发现交换与不交换最多有一个不为空,所以我们完全可以把两种情况拆开。现在问题就是:有若干个点对 ((x_i,y_i))​,查询 (mathrm{dep}_{mathrm{lca}(x_i,x_j)}+mathrm{dep}_{mathrm{lca}(y_i,y_j)}) 的最大值((-2mathrm{dep}_x)​ 直接提出来了)。​

    一个比较有前途的思路是枚举 (a_{i,j}=mathrm{lca}(x_i,x_j)),然后对 (a) 确实为它的 ((i,j)) 进行找 (b_{i,j}=mathrm{lca}(y_i,y_j))​​ 的最大深度。一个套路是:某两个点的 lca 等于某个点不太好搞,但是在这个点的子树内是极其容易的,就相当于都在这个点的子树内嘛(话说这个跟某些高维差分、莫反异曲同工,总之就是算出更容易计算的前缀和 / 差分数组)。并且在这个问题上面我们不需要什么差分,直接把问题转化为两个点都在该子树内即可,因为实际 lca 更低的一定会在真后代处被统计,直接覆盖掉了。

    那么对某个 (a) 其实问题就是将 (x_iinmathrm{subt}(a))(y_i) 全部拿出来,然后两两 lca,求最大深度。跟据欧拉序 + st 表求 lca 的理论,显然是按照 dfn 排序之后相邻的才会奏效。于是我们立刻想出来一个 dsu + set 的做法,每次 insert 便找前驱后继更新答案,应该是大常数 2log。然而其实也可以改成线段树合并,合并的时候使用套路的线段树上 cdq 来更新答案即可(需要维护区间最大 / 最小非空位置)。

    然而对于每个桶都这么搞,它跟 (n) 有关的,所以会炸成 (Omega!left(n^2 ight))。你会发现其实只要对所有关键点建虚树即可解决所有问题!这一部分总复杂度是 1log。


    最后总复杂度是 2log,复杂度瓶颈在于的树上倍增 + 线段树区间查询,但是线段树合并常数也是非常大的,所以略微卡常。nfls20034 上 T 成 90,但是 loj 神机自然是让我过了。

    另外我被这题卡空间卡了好久(傻逼 loj 评测机宕机):毕竟 ML 只有 256MB。我做了一些卡空间措施:

    1. 将我的两个线段树合并的 node 数组共用(反正两个部分分开来计算)。
    2. 将 st 表的 pair 换成 int,然后用 cmp 来比 min。
    3. 各种 vector 数组(有好多)全部换成链式前向星,毕竟 vector 静态内存就比前向星大了,再加上 1.5 倍的动态内存。。。。

    最后 230+MB 卡过去了。

    7k 的代码 @_@(其实感觉把两个线段树合并都改成 dsu + set 就还好?)
    #include<bits/stdc++.h>
    using namespace std;
    #define mp make_pair
    #define X first
    #define Y second
    #define pb push_back
    const int inf=0x3f3f3f3f;
    const int N=4e5+10,LOG_N=20;
    bool stO;
    int n,m;
    struct addedge{
    	int sz,head[N/2],nxt[N],val[N];
    	addedge(){sz=0;memset(head,0,sizeof(head));}
    	void ae(int x,int y){
    		sz++;val[sz]=y;nxt[sz]=head[x],head[x]=sz;
    	}
    };
    addedge nei;
    int a[N/2],b[N/2];
    int fa[N/2][LOG_N];
    int dfn[N/2],nowdfn,mng[N/2],mxdfn[N/2],dep[N/2];
    int euler[N],fst[N/2],eulernow;
    void dfs1(int x=1){
    	mng[dfn[x]=mxdfn[x]=++nowdfn]=x;
    	euler[++eulernow]=x,fst[x]=eulernow;
    	for(int i=1;i<LOG_N;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
    	for(int i=nei.head[x];i;i=nei.nxt[i]){
    		int y=nei.val[i];
    		if(y==fa[x][0])continue;
    		fa[y][0]=x;
    		dep[y]=dep[x]+1;
    		dfs1(y);
    		euler[++eulernow]=x;
    		mxdfn[x]=mxdfn[y];
    	}
    }
    bool cd(int x,int y){return dep[x]<dep[y];}
    struct stable{
    	int mn[N][LOG_N];int _log[N];
    	void init(){
    		for(int i=2;i<=2*n;i++)_log[i]=_log[i-1]+(1<<_log[i-1]+1==i);
    		for(int i=1;i<2*n;i++)mn[i][0]=euler[i];
    		for(int j=1;j<LOG_N;j++)for(int i=1;i+(1<<j)-1<2*n;i++)mn[i][j]=min(mn[i][j-1],mn[i+(1<<j-1)][j-1],cd);
    	}
    	int _mn(int l,int r){
    		int log0=_log[r-l+1];
    		return min(mn[l][log0],mn[r-(1<<log0)+1][log0],cd);
    	}
    }st;
    int lca(int x,int y){
    	x=fst[x],y=fst[y];
    	if(x>y)swap(x,y);
    	return st._mn(x,y);
    }
    pair<int,pair<int,int> > ans;
    addedge buc;
    int stk[N/2],top;
    addedge son;
    bool cmp(int x,int y){return dfn[x]<dfn[y];}
    void virtree(vector<int> &v){
    	v.pb(1);
    	sort(v.begin(),v.end(),cmp);
    	int tmp=v.size();
    	for(int i=0;i+1<tmp;i++)v.pb(lca(v[i],v[i+1]));
    	sort(v.begin(),v.end(),cmp);v.resize(unique(v.begin(),v.end())-v.begin());
    	top=0;
    	for(int i=0;i<v.size();i++){
    		int x=v[i];
    		while(top&&!(dfn[stk[top-1]]<=dfn[x]&&dfn[x]<=mxdfn[stk[top-1]]))top--;
    		if(top)son.ae(stk[top-1],x);
    		stk[top++]=x;
    	}
    }
    struct node{int ls,rs,mn,mx;}nd[N*LOG_N];
    struct segtree{
    	int sz,root[N/2];
    	#define ls(p) nd[p].ls
    	#define rs(p) nd[p].rs
    	#define mn(p) nd[p].mn
    	#define mx(p) nd[p].mx
    	int nwnd(){return nd[++sz]=nd[0],sz;}
    	void init(vector<int> &v){
    		nd[sz=0]=node({0,0,inf,-inf});
    		for(int i=0;i<v.size();i++)root[v[i]]=nwnd();
    	}
    	void sprup(int p){mn(p)=min(mn(ls(p)),mn(rs(p))),mx(p)=max(mx(ls(p)),mx(rs(p)));}
    	void insert(int x,int p,int tl=1,int tr=n){
    		if(tl==tr)return mn(p)=mx(p)=x,void();
    		int mid=tl+tr>>1;
    		if(x<=mid){
    			if(!ls(p))ls(p)=nwnd();
    			insert(x,ls(p),tl,mid);
    		}
    		else{
    			if(!rs(p))rs(p)=nwnd();
    			insert(x,rs(p),mid+1,tr);
    		}
    		sprup(p);
    	}
    	int mrg(int p,int q,pair<int,int> &upd,int tl=1,int tr=n){
    		if(!p||!q)return p|q;
    		if(tl==tr)return upd=max(upd,mp(dep[mng[tl]],mng[tl])),p;
    		int mid=tl+tr>>1;
    		if(ls(p)&&rs(q)){
    			int ca=lca(mng[mx(ls(p))],mng[mn(rs(q))]);
    			upd=max(upd,mp(dep[ca],ca));
    		}
    		if(ls(q)&&rs(p)){
    			int ca=lca(mng[mx(ls(q))],mng[mn(rs(p))]);
    			upd=max(upd,mp(dep[ca],ca));
    		}
    		ls(p)=mrg(ls(p),ls(q),upd,tl,mid),rs(p)=mrg(rs(p),rs(q),upd,mid+1,tr);
    		return sprup(p),p;
    	}
    }segt;
    vector<int> vec[N];
    void dfs2(int x,int root){
    	pair<int,int> res(-inf,0);
    	sort(vec[x].begin(),vec[x].end(),cmp);
    	for(int i=0;i+1<vec[x].size();i++){
    		int ca=lca(vec[x][i],vec[x][i+1]);
    		res=max(res,mp(dep[ca],ca));
    	}
    	for(int i=0;i<vec[x].size();i++)segt.insert(dfn[vec[x][i]],segt.root[x]);
    	for(int i=son.head[x];i;i=son.nxt[i]){
    		int y=son.val[i];
    		dfs2(y,root);
    		segt.root[x]=segt.mrg(segt.root[x],segt.root[y],res);
    	}
    	ans=max(ans,mp(res.X+dep[x]-2*dep[root],mp(res.Y,x)));
    //	cout<<x<<":"<<res.X+dep[x]-2*dep[root]<<"!
    ";
    }
    void sol(int x){
    //	puts("--------");
    	vector<int> v;
    	for(int i=buc.head[x];i;i=buc.nxt[i])v.pb(a[buc.val[i]]),v.pb(b[buc.val[i]]);
    	virtree(v);
    	for(int i=buc.head[x];i;i=buc.nxt[i])vec[a[buc.val[i]]].pb(b[buc.val[i]]),vec[b[buc.val[i]]].pb(a[buc.val[i]]);
    	segt.init(v);
    	dfs2(1,x);
    	son.sz=0;
    	for(int i=0;i<v.size();i++)son.head[v[i]]=0,vec[v[i]].clear();
    }
    addedge add,del;
    struct segtree0{
    	int sz,root[N];
    	#define cnt(p) nd[p].mn
    	int nwnd(){return nd[++sz]=nd[0],sz;}
    	void init(){
    		nd[sz=0]=node({0,0,0,0});
    		for(int i=1;i<=n;i++)root[i]=nwnd();
    	}
    	void sprup(int p){cnt(p)=cnt(ls(p))+cnt(rs(p));}
    	void add(int x,int v,int p,int tl=1,int tr=n){
    		if(tl==tr)return cnt(p)+=v,void();
    		int mid=tl+tr>>1;
    		if(x<=mid){
    			if(!ls(p))ls(p)=nwnd();
    			add(x,v,ls(p),tl,mid);
    		}
    		else{
    			if(!rs(p))rs(p)=nwnd();
    			add(x,v,rs(p),mid+1,tr);
    		}
    		sprup(p);
    	}
    	int mrg(int p,int q,int tl=1,int tr=n){
    		if(!p||!q)return p|q;
    		if(tl==tr)return cnt(p)+=cnt(q),p;
    		int mid=tl+tr>>1;
    		ls(p)=mrg(ls(p),ls(q),tl,mid),rs(p)=mrg(rs(p),rs(q),mid+1,tr);
    		return sprup(p),p;
    	}
    	int _cnt(int l,int r,int p,int tl=1,int tr=n){
    		if(l<=tl&&r>=tr)return cnt(p);
    		int mid=tl+tr>>1,res=0;
    		if(l<=mid)res+=_cnt(l,r,ls(p),tl,mid);
    		if(r>mid)res+=_cnt(l,r,rs(p),mid+1,tr);
    		return res;
    	}
    }segt0;
    void dfs3(int x=1){
    	for(int i=add.head[x];i;i=add.nxt[i])segt0.add(dfn[add.val[i]],1,segt0.root[x]);
    	for(int i=del.head[x];i;i=del.nxt[i])segt0.add(dfn[del.val[i]],-1,segt0.root[x]);
    	for(int i=nei.head[x];i;i=nei.nxt[i]){
    		int y=nei.val[i];
    		if(y==fa[x][0])continue;
    		dfs3(y);
    		segt0.root[x]=segt0.mrg(segt0.root[x],segt0.root[y]);
    	}
    	vector<int> todo;
    	for(int i=buc.head[x];i;i=buc.nxt[i])todo.pb(a[buc.val[i]]),todo.pb(b[buc.val[i]]);
    	for(int i=0;i<todo.size();i++){
    		int y=todo[i];
    		for(int j=LOG_N-1;~j;j--){
    //			cout<<fa[y][j]<<":"<<dfn[fa[y][j]]<<" "<<mxdfn[fa[y][j]]<<"?
    ";
    			if(dep[fa[y][j]]>=dep[x])assert(segt0._cnt(dfn[fa[y][j]],mxdfn[fa[y][j]],segt0.root[x]));
    			if(dep[fa[y][j]]>=dep[x]&&segt0._cnt(dfn[fa[y][j]],mxdfn[fa[y][j]],segt0.root[x])==1)y=fa[y][j];
    //			cout<<fa[y][j]<<"??
    ";
    		}
    		if(segt0._cnt(dfn[y],mxdfn[y],segt0.root[x])==1)y=fa[y][0];
    		ans=max(ans,mp(dep[y]-dep[x],mp(x,y)));
    	}
    }
    int dis(int x,int y){return dep[x]+dep[y]-2*dep[lca(x,y)];}
    int in(int x,int y,int z){return dis(x,y)+dis(x,z)==dis(y,z);}
    bool Orz;
    int main(){
    //	freopen("tree.in","r",stdin);freopen("tree.out","w",stdout);
    //	cout<<(&stO-&Orz)/1024/1024;
    	cin>>n>>m;
    	for(int i=2;i<=n;i++){
    		int x;
    		scanf("%d",&x);
    		nei.ae(x,i),nei.ae(i,x);
    	}
    	for(int i=1;i<=m;i++)scanf("%d%d",a+i,b+i);
    	dep[1]=1,dfs1(),st.init();
    	for(int i=1;i<=m;i++)buc.ae(lca(a[i],b[i]),i);
    //	cout<<lca(b[55],b[17])<<"!!
    ";
    	for(int i=1;i<=n;i++)sol(i);
    //	cout<<ans.X<<"!!
    ";
    	segt0.init();
    	for(int j=1;j<=n;j++){
    		int x=mng[j];
    		for(int i=buc.head[x];i;i=buc.nxt[i]){
    			add.ae(a[buc.val[i]],a[buc.val[i]]),del.ae(fa[x][0],a[buc.val[i]]);
    			add.ae(b[buc.val[i]],b[buc.val[i]]),del.ae(fa[x][0],b[buc.val[i]]);
    		}
    	}
    	dfs3();
    	cout<<ans.X<<"
    ";
    	int x=ans.Y.X,y=ans.Y.Y;
    //	cout<<x<<" "<<y<<" hscsb
    ";
    	if(!ans.X)puts("1 2");
    	else{
    		vector<int> v;
    		for(int i=1;i<=m;i++)if(in(x,a[i],b[i])&&in(y,a[i],b[i]))v.pb(i);
    		assert(v.size()>=2);
    		cout<<v[0]<<" "<<v[1]<<"
    ";
    	}
    	return 0;
    }
    
    珍爱生命,远离抄袭!
  • 相关阅读:
    Hibernate:组合模式解决树的映射
    以面到点的学习MFC
    linux内核--进程与线程
    控件自定义
    火车车次查询-余票查询--Api接口
    如何处理大量数据并发操作(数据库锁机制详解)
    Java单链表、双端链表、有序链表实现
    事务、数据库事务、事务隔离级别、锁的简单总结
    数据库连接池分析
    Spring面试题集
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/solution-loj3066.html
Copyright © 2020-2023  润新知