• 异象石(不用引理的乱搞做法)


    QAQ 237行的代码 QAQ

    打了我一个下午啊啊啊啊。

    题目

    题目

    我的做法

    基础做法

    机房大佬CLB提示我这道题目是虚树(当然我的做法不知道是不是虚树,反正虚就对了),于是我发现对于以下的图:在这里插入图片描述
    在对外显示上,两个蓝点的LCA是可以代替这两个蓝点的,然后,我们的做法出来了,对于两个点,维护他们两个的LCA,如果一个虚点(即不是真的异象石的位置)只有一个点(不管实的虚的),那么就将这个点连到其父亲上面。

    特别的,因为(个人觉得)难以维护目前树上没有父亲的点,所以默认(1)点永远存在。(当然后来发现没有父亲的点最多一个,其实用个(root)存一下就行了)

    当然,还是有不少细节的。

    给上一个图(绿色为虚点,蓝色为实点,黑色为原树上的边,红色为虚树上的边):

    在这里插入图片描述

    注:一下内容的儿子、父亲、LCA可能有点难分辨是实树上的还是虚树上的,同时以下都以插入为例。

    如何在log时间内查询一个点最近的父亲?

    处理出DFS序后,我们新建了一个点,那么对于这个这个新建的点,如果是叶子节点的,那么整个子树(实树)直接认为其是父亲(用线段树实现),但是如果是非叶子节点,可以发现,他建立时最多只有两个儿子(虚树)节点(因为我们只要两个点有了LCA(实树)就建),然后把DFS序分成两段即可,但是需要注意的是,不一定是两个儿子,有时候只有一个儿子,因为LCA可能是其自己。

    如何在log的时间内查询一个点的某个儿子的子树内的儿子节点编号

    首先一个点的一个儿子(原树)的子树内的只可能存在一个儿子(虚树),两个的话他们必然会产生一个LCA,那么怎么查到这个点就是重中之重的事了,由于实树儿子的编号是固定的,我们可以在这个点的平衡树中插入其儿子的编号,而附加权值就是这个儿子子树中的虚树儿子编号。

    实现

    由于(1)一直都在,所以(zans)表示到(1)点的距离,根据要不要经过(1)决定加还是不加。(本人觉得(root)的实现会更简单吧)

    当然只有亿点细节。

    时间复杂度:(O(nlogn)),以及亿点点的常数,但也许是因为平衡树跑不满,或者因为正解用了set的缘故,我跑的竟然比正解快???

    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<ctime>
    #define  N  110000
    #define  M  210000
    using  namespace  std;
    typedef  long  long  LL;
    //FHQ treap(平衡树)
    int  val[M],ke1[M],ke2[M],tot,rt[M],siz[N],son[M][2];bool  li[N]/*这个点是否已经存在异象石*/;//最上层的点全部归到1 
    void  spilt(int  now,int  k,int  &A,int  &B)
    {
    	if(!now)A=B=0;
    	else
    	{
    		if(ke1[now]<=k)A=now,spilt(son[A][1],k,son[A][1],B);
    		else  B=now,spilt(son[B][0],k,A,son[B][0]);
    	}
    }
    int  mer(int  A,int  B)
    {
    	if(!A  ||  !B)return  A+B;
    	if(val[A]<=val[B])son[A][1]=mer(son[A][1],B);
    	else  son[B][0]=mer(A,son[B][0]),A^=B^=A^=B;
    	return  A; 
    }
    inline  void  add(int  id,int  k1,int  k2)
    {
    	tot++;val[tot]=rand();ke1[tot]=k1;ke2[tot]=k2;
    	int  x,y;spilt(rt[id],k1,x,y);
    	rt[id]=mer(mer(x,tot),y);
    }
    inline  void  del(int  id,int  k)
    {
    	int  x,y,z;spilt(rt[id],k,x,z);spilt(x,k-1,x,y);
    	rt[id]=mer(x,z);
    }
    inline  void  change(int  id,int  k1,int  k2)
    {
    	int  x,y,z;spilt(rt[id],k1,x,z);spilt(x,k1-1,x,y);
    	ke2[y]=k2;
    	rt[id]=mer(x,mer(y,z));
    }
    inline  int  findz(int  id,int  k)//这个子树是否存在虚树的儿子 
    {
    	int  bk=0;
    	int  x,y,z;spilt(rt[id],k-1,x,y);spilt(y,k,y,z);
    	if(y)bk=ke2[y];
    	rt[id]=mer(mer(x,y),z);
    	return  bk;
    }
    //线段树 
    struct  TREE
    {
    	int  l,r,c;
    }tr[M];int  tlen;
    void  bt(int   l,int  r)
    {
    	int  now=++tlen;
    	if(l<r)
    	{
    		int  mid=(l+r)>>1;
    		tr[now].l=tlen+1;bt(l,mid);
    		tr[now].r=tlen+1;bt(mid+1,r);
    	}
    	else  tr[now].c=1;
    }
    inline  void  set_c(int  now,int  k){tr[now].c=k;}
    inline  void  downdate(int  now)
    {
    	if(tr[now].c)set_c(tr[now].l,tr[now].c),set_c(tr[now].r,tr[now].c),tr[now].c=0;
    }
    void  CHG(int  now,int  l,int  r,int  ll,int  rr,int  k)
    {
    	if(ll>rr)return  ;
    	if(l==ll  &&  r==rr){set_c(now,k);return  ;}
    	downdate(now);
    	int  mid=(l+r)/2;
    	if(rr<=mid)CHG(tr[now].l,l,mid,ll,rr,k);
    	else  if(mid<ll)CHG(tr[now].r,mid+1,r,ll,rr,k);
    	else  CHG(tr[now].l,l,mid,ll,mid,k),CHG(tr[now].r,mid+1,r,mid+1,rr,k);
    }
    int  findans(int  now,int  l,int  r,int  id)
    {
    	if(tr[now].c)return  tr[now].c;
    	int  mid=(l+r)/2;
    	if(id<=mid)return  findans(tr[now].l,l,mid,id);
    	else  return  findans(tr[now].r,mid+1,r,id);
    }
    //边目录 
    struct  node
    {
    	int  y,c,next;
    }a[M];int  len,last[N];
    inline  void  ins(int  x,int  y,int  c){len++;a[len].y=y;a[len].c=c;a[len].next=last[x];last[x]=len;}
    //LCA
    int  fa[N][20],top,be[N],ll[N],rr[N],dep[N];
    LL  dis[N];
    void  dfs(int  x)
    {
    	++top;be[x]=top;ll[x]=top;
    	for(int  i=1;i<=18;i++)
    	{
    		fa[x][i]=fa[fa[x][i-1]][i-1];
    		if(!fa[x][i])break;
    	}
    	for(int  k=last[x];k;k=a[k].next)
    	{
    		int  y=a[k].y;
    		if(y!=fa[x][0])dis[y]=dis[x]+a[k].c,dep[y]=dep[x]+1,fa[y][0]=x,dfs(y);
    	}
    	rr[x]=top;
    }
    int  lca(int  x,int  y)
    {
    	for(int  i=18;i>=0;i--)
    	{
    		if(dep[fa[x][i]]>dep[y])x=fa[x][i];
    	}
    	return  x;
    }
    int  lcac(int  x,int  y)
    {
    	if(dep[x]>dep[y])x^=y^=x^=y;
    	for(int  i=18;i>=0;i--)
    	{
    		if(dep[fa[y][i]]>=dep[x])y=fa[y][i];
    	}
    	if(x==y)return  y;
    	for(int  i=18;i>=0;i--)
    	{
    		if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
    	}
    	return  fa[x][0];
    }
    int  n,m;
    LL  ans,zans/*表示连向1的点的距离,如果siz[1]>1则可以加入*/;
    inline  void  del_dian(int  x,int  fat=0)//删除一个点,仅当这个点只有一个儿子的时候 
    {
    	if(li[x]  ||  x==1)return  ;//如果他只是单纯的自己一个点的话,或者说他是1的话,那没事了 
    	if(!fat)fat=findans(1,1,n,be[fa[x][0]]);//找到x的父亲
    	int  z=lca(x,fat),t=ke2[rt[x]]/*找到其唯一的一个节点*/;
    	//删除x 
    	del(x,ke1[rt[x]]);siz[x]=0;
    	CHG(1,1,n,ll[x],ll[t]-1,fat);
    	CHG(1,1,n,rr[t]+1,rr[x],fat);
    	
    	//插入 
    	change(fat,z,t);
    	
    	if(fat==1)
    	{
    		ans-=dis[t]-dis[x];
    		zans+=dis[t]-dis[x];
    	}
    	
    }
    int  main()
    {
    	srand(666);
    	dep[0]=-1;
    	scanf("%d",&n);
    	for(int  i=1;i<n;i++)
    	{
    		int  x,y,c;scanf("%d%d%d",&x,&y,&c);
    		ins(x,y,c);ins(y,x,c);
    	}
    	dfs(1);
    	scanf("%d",&m);
    	bt(1,n);
    	for(int  i=1;i<=m;i++)
    	{
    		char  st[10];scanf("%s",st);
    		if(st[0]=='+')
    		{
    			int  x;scanf("%d",&x);
    			if(!li[x])//插入点 
    			{
    				li[x]=1;siz[x]++;//他本身自己就是一个点 
    				int  y=findans(1,1,n,be[x])/*表示父亲节点*/;
    				if(x!=y)
    				{
    					int  z=lca(x,y)/*y的下面*/,t/*和x结合的点*/;
    					if((t=findz(y,z)))//什么,有点可以结合 
    					{
    						//先把x,t插入到k中 
    						int  k=lcac(t,x);//新建立一个点代表他们两个
    						int  shit;
    						if(k!=x)shit=lca(x,k),add(k,shit,x);//自己怎么能加入自己呢? 
    						shit=lca(t,k),add(k,shit,t);//t肯定不等于k,否则x的父亲就是t了 
    						siz[k]=2;//有两个点 
    						CHG(1,1,n,ll[k],ll[t]-1,k);//你们的父亲都是我 
    						CHG(1,1,n,rr[t]+1,rr[k],k);//你们的父亲都是我 
    						//此时的k已经可以等效x,t了 
    						change(y,z,k);//将原本t的位置改成k 
    						if(y==1)
    						{
    							ans+=dis[t]+dis[x]-2*dis[k];
    							zans+=dis[k]-dis[t];
    						}
    						else  ans+=dis[x]-dis[k];
    						if(k!=x)CHG(1,1,n,ll[x],rr[x],x);
    					}
    					else
    					{
    						add(y,z,x),siz[y]++;//没有就加入 
    						if(y!=1)ans+=dis[x]-dis[y];
    						else  zans+=dis[x]-dis[y];
    						CHG(1,1,n,ll[x],rr[x],x);
    					}
    				}
    			}
    		}
    		else  if(st[0]=='-')
    		{
    			int  x;scanf("%d",&x);
    			if(li[x])//插入点 
    			{
    				int  y=findans(1,1,n,be[fa[x][0]])/*表示父亲节点*/;
    				li[x]=0;siz[x]--;
    				if(!siz[x])//只有一个点 
    				{
    					CHG(1,1,n,ll[x],rr[x],y);
    					int  z=lca(x,y);del(y,z);siz[y]--;
    					if(y==1)zans-=dis[x];
    					else  ans-=dis[x]-dis[y];
    					if(siz[y]==1)del_dian(y);
    				}
    				else  if(siz[x]==1)del_dian(x,y);//只剩下子树中的一个点,破坏掉这个原有的结构 
    			}
    		}
    		else
    		{
    			printf("%lld
    ",ans+zans*(siz[1]>1));
    		}
    	}
    	return  0;
    }
    

    正解

    先求出树上点的DFS序,将异象石按DFS序排序,连成一个环,然后将相邻两个异象石距离相加,得到的就是答案的两倍。

    严谨参照 https://www.cnblogs.com/2aptx4869/p/13513541.html (虽然我并不知道为什么他还要枚举(a_3)(a_2)在树上的相对位置)

    但是我的证明是,将其像我的做法一样处理(但是(1)不是严格存在了,严格存在只是因为代码难处理)

    然后得到了一棵虚树,不难想到一定存在一种遍历方案,使得实树中按异象石的DFS排列后的异象石序列,和虚树中按异象石的DFS排列的异象石序列相同。(简单来说就是在虚树中遍历跟在实树中遍历是一样的,所以这个证明放到实树遍历一样的,虚树更加直观)。

    接下来我会证明,这个加法的过程,其实就是(DFS)遍历中进入和回溯时经过的边的权值的总和。

    对于一个实点而言,你在遍历的过程中不管是回溯还是进入,你遍历到的下一个实点,就是你DFS序的后一位,而进入回溯所记录的边的总和,就是你们两个的距离(否则中间必然存在其他实点,因为叶子节点都是实点)。

    不过刚开始从根节点走到(1) 号实点以及(n)号实点回溯到根节点记录的边权,你可以认为加起来是(n)号实点在找(1)号实点。

    同时这个虚树所有边的权值就是答案,而DFS所记录的边权其实就是所有边权值和的两倍。

    证毕。

    至于维护DFS序的排序,用的是set。

    代码也是抄的QMQ。

    时间复杂度:(O(nlogn))

    #include <bits/stdc++.h>
    #define all(n) (n).begin(), (n).end()
    #define se second
    #define fi first
    #define pb push_back
    #define mp make_pair
    #define sqr(n) (n)*(n)
    #define rep(i,a,b) for(int i=(a);i<=(b);++i)
    #define per(i,a,b) for(int i=(a);i>=(b);--i)
    #define IO ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr)
    using namespace std;
    typedef long long ll;
    typedef pair<int, int> PII;
    typedef pair<ll, ll> PLL;
    typedef vector<int> VI;
    typedef double db;
    
    const int N = 1e5 + 5;
    
    int n, m, _, k;
    int h[N], ne[N << 1], to[N << 1], co[N << 1], tot;
    int dfn[N], cnt, d[N], f[N][30], t;
    ll dist[N], ans;
    char s[3];
    set<PII> st;
    
    void add(int u, int v, int c) {
        ne[++tot] = h[u]; h[u] = tot; to[tot] = v; co[tot] = c;
    }
    
    void bfs(int s) {
        queue<int> q;
        q.push(s); d[s] = 1; dist[s] = 0;
        rep (i, 0, t) f[s][i] = 0;
    
        while (!q.empty()) {
            int x = q.front(); q.pop();
            for (int i = h[x]; i; i = ne[i]) {
                int y = to[i];
                if (d[y]) continue;
                d[y] = d[x] + 1;
                dist[y] = dist[x] + co[i];
                f[y][0] = x;
    
                for (int j = 1; j <= t; ++j) 
                    f[y][j] = f[f[y][j - 1]][j - 1];
    
                q.push(y);
            }
        }
    }
    
    int lca(int x, int y) {
        if (d[x] > d[y]) swap(x, y);
        per (i, t, 0) 
            if (d[f[y][i]] >= d[x]) y = f[y][i];
    
        if (x == y) return x;
    
        per (i, t, 0)
            if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
    
        return f[x][0]; 
    }
    
    void dfs(int u) {
        dfn[u] = ++cnt;
        for (int i = h[u]; i; i = ne[i]) {
            int y = to[i];
            if (dfn[y]) continue;
            dfs(y);
        }
    }
    
    void worka() {
        auto it = st.upper_bound({ dfn[k], k });
        int x, y;
        if (it == st.begin() || it == st.end()) x = st.begin()->se, y = st.rbegin()->se;
        else  x = it->se, y = (--it)->se;
    
        ans += (dist[k] << 1) - ((dist[lca(k, x)] + dist[lca(k, y)] - dist[lca(x, y)]) << 1);
        st.insert({ dfn[k], k });
    }
    
    void workb() {
        if (st.size() == 1) { st.clear(); return; }
    
        auto it = st.lower_bound({ dfn[k], k }), ita = it; ++ita;
        int x, y;
        if (ita == st.end()) y = st.begin()->se;
        else y = ita->se;
        if (it == st.begin()) x = st.rbegin()->se;
        else x = (--it)->se;
    
        ans -= (dist[k] << 1) - ((dist[lca(k, x)] + dist[lca(k, y)] - dist[lca(x, y)]) << 1);
        st.erase(--ita);
    }
    
    int main() {
        IO; cin >> n;
        rep(i, 2, n) {
            int u, v, c; cin >> u >> v >> c;
            add(u, v, c); add(v, u, c);
        }
    
        t = log2(n - 1) + 1;
        dfs(1); bfs(1);
    
        cin >> m;
        rep(i, 1, m) {
            cin >> s;
            if (s[0] == '?') cout << (ans >> 1) << '
    ';
            else {
                cin >> k;
                if (st.empty()) st.insert({ dfn[k], k });
                else if (s[0] == '+') worka();
                else workb();
            }
        }
        return 0;
    }
    
    
  • 相关阅读:
    CentOS7和Ubuntu18.10下运行Qt Creator出现cannot find -lGL的问题的解决方案
    C++中使用CMake编译管理项目
    C++ std::isnan等函数的使用
    七个开源的 Spring Boot 前后端分离项目,一定要收藏!
    基于Select模型的Windows TCP服务端和客户端程序示例
    简单的C++11线程池实现
    Windows下FFMEPG编译
    MIMO431学习笔记目录
    golang调用海康sdk
    GRPC-go版本
  • 原文地址:https://www.cnblogs.com/zhangjianjunab/p/13681390.html
Copyright © 2020-2023  润新知