• 树形结构升级训练 习题


    标签(空格分隔): 517coding solution problem


    Task 1

    对边进行树上差分。

    考虑到一条路径(u - v) 可以将$c_u +=1 ,c_v +=1 ,c_{lca(u,v)}-=2 $

    然后对整一棵树求子树和,对于每个点的子树和,就是这个点向上那条边的答案。

    复杂度(O(n log n))

    # include <bits/stdc++.h>
    using namespace std;
    const int N=1e5+10;
    struct rec{ int pre,to; }a[N<<1];
    int head[N],tot=1,ans[N],c[N],dep[N],g[N][22],n,m;
    void adde(int u,int v)
    {
    	a[++tot].pre=head[u];
    	a[tot].to=v;
    	head[u]=tot;
    }
    void dfs(int u,int fa)
    {
    	dep[u]=dep[fa]+1; g[u][0]=fa;
    	for (int i=head[u];i;i=a[i].pre) {
    		int v=a[i].to; if (v==fa) continue;
    		dfs(v,u);
    	}
    }
    void init()
    {
    	dfs(1,0);
    	for (int i=1;i<=21;i++)
    	 for (int j=1;j<=n;j++)
    	  g[j][i]=g[g[j][i-1]][i-1];
    }
    int lca(int u,int v)
    {
    	if (dep[u]<dep[v]) swap(u,v);
    	for (int i=21;i>=0;i--)
    	 if (dep[g[u][i]]>=dep[v]) u=g[u][i];
    	if (u==v) return u;
    	for (int i=21;i>=0;i--)
    	 if (g[u][i]!=g[v][i]) u=g[u][i],v=g[v][i];
    	return g[u][0];
    }
    void dfs2(int u,int fa,int to)
    {
    	for (int i=head[u];i;i=a[i].pre) {
    		int v=a[i].to; if (v==fa) continue;
    		dfs2(v,u,i>>1);c[u]+=c[v];
    	}
    	ans[to]=c[u];
    }
    int main()
    {
    	scanf("%d",&n);
    	for (int i=1;i<n;i++) {
    		int u,v;scanf("%d%d",&u,&v);
    		adde(u,v); adde(v,u);
    	}
    	scanf("%d",&m);
    	init();
    	for (int i=1;i<=m;i++) {
    		int u,v;scanf("%d%d",&u,&v);
    		c[u]++; c[v]++; c[lca(u,v)]-=2;
    	}
    	dfs2(1,0,0);
    	for (int i=1;i<=n-1;i++) printf("%d ",ans[i]);
    	return 0;
    }
    

    Task 2

    首先将题目所给的相连的黑色点和白色点都缩成一个点。

    然后这棵树就变成黑白相间的点了。

    那么如果对于某一点进行操作势必可以从改点为中心向外辐射,不断可以把该点为中心的连通块染成同一颜色。

    for example , 1 0 1 0 1 第一次可以变成1 0 0 0 1 第二次可以变成 1 1 1 1 1 。

    那么对于一棵树,操作的最优的点显然是树的直径的中点附近。

    所有问题有转化为将树的直径染色使得直径变成同一颜色。

    考虑若长度为奇数 : 形如 1 0 1 ,答案就是 len/2
    若长度为偶数:形如 1 0 ,答案就是len/2

    复杂度是(O(n))

    # include <bits/stdc++.h>
    using namespace std;
    const int N=2e5+10;
    struct rec{
    	int pre,to;
    }a[N<<1];
    int w[N],head[N],n,m,tot,d[N],belong[N];
    vector<int>E[N];
    void adde(int u,int v)
    {
    	a[++tot].pre=head[u];
    	a[tot].to=v;
    	head[u]=tot;
    }
    void dfs1(int u,int num)
    {
    	belong[u]=num;
    	for (int i=head[u];i;i=a[i].pre) {
    		int v=a[i].to;
    		if ((belong[v]==-1) && (w[v]==0)) dfs1(v,num);
    	}
    }
    void dfs2(int u,int num)
    {
    	belong[u]=num;
    	for (int i=head[u];i;i=a[i].pre) {
    		int v=a[i].to;
    		if ((belong[v]==-1) && (w[v]==1)) dfs2(v,num);
    	}
    }
    void dfs3(int u,int fa,int L)
    {
    	d[u]=L;
    	for (int i=0;i<E[u].size();i++)
    	 if (E[u][i]!=fa) dfs3(E[u][i],u,L+1);
    }
    int main()
    {
    	scanf("%d",&n);
    	for (int i=1;i<=n;i++) scanf("%d",&w[i]);
    	for (int i=1;i<n;i++) {
    		int u,v; scanf("%d%d",&u,&v);
    		adde(u,v); adde(v,u);
    	}
    	memset(belong,-1,sizeof(belong)); int tt=0;
    	for (int i=1;i<=n;i++) if ((belong[i]==-1) && (w[i]==0)) tt++,dfs1(i,tt);
    	for (int i=1;i<=n;i++) if ((belong[i]==-1) && (w[i]==1)) tt++,dfs2(i,tt);
    	
    	for (int u=1;u<=n;u++) for (int i=head[u];i;i=a[i].pre) {
    		int v=a[i].to;
    		if (belong[v]!=belong[u]) E[belong[v]].push_back(belong[u]);
    	}
    	for (int i=1;i<=tt;i++){
    		int tmp=unique(E[i].begin(),E[i].end())-E[i].begin();
    		E[i].erase(E[i].begin()+tmp,E[i].end());
    	}
    	memset(d,0,sizeof(d)); int pt=0;
    	dfs3(1,0,1);
    	for (int i=1;i<=tt;i++) if (d[pt]<d[i]) pt=i;
    	memset(d,0,sizeof(d)); int ans=0;
    	dfs3(pt,0,1);
    	for (int i=1;i<=tt;i++) if (ans<d[i]) ans=d[i];
    	printf("%d
    ",ans>>1);
    	return 0;
    }
    

    Task 3

    经典的树上路径交的问题。

    考虑两条路径([a,b] , [c,d])交的条件。

    就是(lca(a,b))([c,d])上或者(lca(c,d))([a,b])上。

    一种比较好的策略是尽可能选择(lca)深度大的路径,这样至少不会坏。

    首先,每一条路径最多对答案做出1的贡献,然后深度如果越小,那么越可能和其他路径相交。如果按照深度一次递增的情况,不会更坏。

    处理的时候,如果(u)为$ lca$的路径是合法的话,那么其子树的其他节点必然不可能成为路径的出发点,所以直接把它的子树赋值为false即可。

    这样子复杂度就是对的了,

    复杂度 $ O(n log n) $

    # include <bits/stdc++.h>
    using namespace std;
    const int N=1e5+10;
    struct node{ int u,v,l;}p[N];
    struct rec{ int pre,to;}a[N<<1];
    int n,m,tot,head[N],dep[N],g[N][22];
    bool vis[N];
    void clear()
    {
    	memset(p,0,sizeof(p)); memset(a,0,sizeof(a));
    	memset(head,0,sizeof(head)); memset(dep,0,sizeof(dep));
    	memset(g,0,sizeof(g)); memset(vis,false,sizeof(vis));
    	tot=0;
    }
    void adde(int u,int v)
    {
    	a[++tot].pre=head[u];
    	a[tot].to=v;
    	head[u]=tot;
    }
    void dfs(int u,int fa)
    {
    	dep[u]=dep[fa]+1; g[u][0]=fa;
    	for (int i=head[u];i;i=a[i].pre) {
    		int v=a[i].to; if (v==fa) continue;
    		dfs(v,u);
    	}
    }
    void init()
    {
    	dfs(1,0);
    	for (int i=1;i<=21;i++)
    	 for (int j=1;j<=n;j++)
    	  g[j][i]=g[g[j][i-1]][i-1];
    }
    int lca(int u,int v)
    {
    	if (dep[u]<dep[v]) swap(u,v);
    	for (int i=21;i>=0;i--)
    	 if (dep[g[u][i]]>=dep[v]) u=g[u][i];
    	if (u==v) return u;
    	for (int i=21;i>=0;i--)
    	 if (g[u][i]!=g[v][i]) u=g[u][i],v=g[v][i];
    	return g[u][0];
    }
    bool cmp(node a,node b){return dep[a.l]>dep[b.l];}
    void draw(int u,int fa)
    {
    	vis[u]=true;
    	for (int i=head[u];i;i=a[i].pre) {
    		int v=a[i].to; if (v==fa||vis[v]) continue;
    		draw(v,u);
    	}
    }
    int main ()
    {
    	while (~scanf("%d%d",&n,&m)) {
    		clear();
    		for (int i=2;i<=n;i++) {
    			int u,v; scanf("%d%d",&u,&v);
    			adde(u,v); adde(v,u);
    		}
    		init();
    		for (int i=1;i<=m;i++) {
    			scanf("%d%d",&p[i].u,&p[i].v);
    			p[i].l=lca(p[i].u,p[i].v);
    		}
    		sort(p+1,p+1+m,cmp);
    		int ans=0; memset(vis,false,sizeof(vis));
    		for (int i=1;i<=m;i++) {
    			if (vis[p[i].u]||vis[p[i].v]) continue;
    			ans++; draw(p[i].l,g[p[i].l][0]);
    		}
    		printf("%d
    ",ans);
    
    	}
    	return 0;
    }
    

    Task 4

    先将树的任意一条直径找出来,考虑树的直径一定是交于一条线段上的。
    那么从直径两段往中间搜一定是中间这一段路径是唯一的。

    设直径是([s,t]),把这个直径拉出来,左侧是(s),右侧是(t);

    • 先以(s)为根,然后跑整一棵树,求出子树最长链和它的条数.
    • (t)开始向左遍历整个直径,找到最左侧的一个点(v)使得其右侧相邻的一条边不是必经边(即使得当前点的右侧最长链条数不发生变化)
    • 然后以(t)为根,然后跑整一棵树,求出子树最长链和它的条数.
    • (s)开始向右遍历整个直径,找到最右侧的一个点(u)使得其左侧相邻的一条边不是必经边(即使得当前点的左侧最长链条数不发生变化)
    • ([u,v])中所有的边都是必经边。

    上述过程显然是一个线性过程,复杂度是(O(n))

    # include <cstdio>
    # include <iostream>
    # include <cstring>
    # define int long long
    using namespace std;
    const int N=2e5+10;
    struct rec{ int pre,to,w;}a[N<<1];
    int n,tot;
    int d[N],head[N],tim[N],f[N],path[N],pre[N];
    void adde(int u,int v,int w)
    {
    	a[++tot].pre=head[u];
    	a[tot].to=v;
    	a[tot].w=w;
    	head[u]=tot;
    }
    void dfs1(int u,int fa,int L)
    {
    	d[u]=L;
    	for (int i=head[u];i;i=a[i].pre) {
    		int v=a[i].to; if (v==fa) continue;
    		dfs1(v,u,L+a[i].w);
    	}
    }
    void dfs2(int u,int fa,int L)
    {
    	d[u]=L;
    	for (int i=head[u];i;i=a[i].pre) {
    		int v=a[i].to; if (v==fa) continue;
    		pre[v]=u; dfs2(v,u,L+a[i].w);
    	}
    }
    void dfs3(int u,int fa)
    {
    	int cnt=0,mx=0; bool leaf=1;
    	for (int i=head[u];i;i=a[i].pre) {
    		int v=a[i].to; if (v==fa) continue; leaf=0;
    		dfs3(v,u);
    		if (f[v]+a[i].w>mx) mx=f[v]+a[i].w,cnt=tim[v];
    		else if (f[v]+a[i].w==mx) cnt+=tim[v];
    	}
    	if (leaf) f[u]=0,tim[u]=1;
    	else f[u]=mx,tim[u]=cnt;
    }
    signed main()
    {
    	scanf("%lld",&n);
    	for (int i=1;i<n;i++) {
    		int u,v,w; scanf("%lld%lld%lld",&u,&v,&w);
    		adde(u,v,w); adde(v,u,w);
    	}
    	memset(d,0,sizeof(d));
    	dfs1(1,0,0); int s=0;
    	for (int i=1;i<=n;i++) if (d[i]>d[s]) s=i;
    	memset(d,0,sizeof(d));
    	dfs2(s,0,0); int t=0; pre[s]=-1;
    	for (int i=1;i<=n;i++) if (d[i]>d[t]) t=i;
    	printf("%lld
    ",d[t]);
    	int u=t,v; while (pre[u]!=-1) path[++path[0]]=u,u=pre[u]; path[++path[0]]=u;
    	for (int i=1;i<=path[0]/2;i++) swap(path[i],path[path[0]-i+1]);
    	memset(f,0,sizeof(f)); memset(tim,0,sizeof(tim)); dfs3(s,0);
    	v=path[0];
    	for (int i=path[0]-1;i>=1;i--)
    	 if (tim[path[i]]-tim[path[i+1]]>0) v=i;
    	memset(f,0,sizeof(f)); memset(tim,0,sizeof(tim)); dfs3(t,0);
    	u=1;
    	for (int i=2;i<=path[0];i++)
    	 if (tim[path[i]]-tim[path[i-1]]>0) u=i;
    	printf("%lld
    ",v-u);
     	return 0;
    }
    

    Task 5

    按照上述做法添加一次叶子,直径最多只可能增加1。

    我们只需要维护当前树的直径(的两个端点)就可以了。

    设之前的端点是(A,B)当前新加入一个叶子(x)

    • (dist(x,A) > Now) : $Now = dist(x,A),B=x $
    • (dist(x,B) > Now) : $Now = dist(x,B),A=x $

    离线处理搞搞(lca)就可以了。

    复杂度是(O((2q+4) log (2q+4)))

    # include <bits/stdc++.h>
    using namespace std;
    const int N=1e6+1000;
    struct rec{ int pre,to;}a[N<<1];
    int head[N],q[N],n,tot,dep[N],g[N][22];
    void adde(int u,int v)
    {
    	a[++tot].pre=head[u];
    	a[tot].to=v;
    	head[u]=tot;
    }
    void dfs(int u,int fa)
    {
    	g[u][0]=fa; dep[u]=dep[fa]+1;
    	for (int i=head[u];i;i=a[i].pre) {
    		int v=a[i].to; if (v==fa) continue;
    		dfs(v,u);
    	}
    }
    void init()
    {
    	dfs(1,0);
    	for (int i=1;i<=21;i++)
    	 for (int j=1;j<=n;j++)
    	  g[j][i]=g[g[j][i-1]][i-1];
    }
    int lca(int u,int v)
    {
    	if (dep[u]<dep[v]) swap(u,v);
    	for (int i=21;i>=0;i--)
    	 if (dep[g[u][i]]>=dep[v]) u=g[u][i];
    	if (u==v) return u;
    	for (int i=21;i>=0;i--)
    	 if (g[u][i]!=g[v][i]) u=g[u][i],v=g[v][i];
    	return g[u][0];
    }
    int dist(int x,int y)
    {
    	int l=lca(x,y);
    	return (dep[x]-dep[1]+dep[y]-dep[1])-2*(dep[l]-1);
    }
    int main()
    {
    	//freopen("1.in","r",stdin);
    	int Q; scanf("%d",&Q);
    	n=4; adde(1,2); adde(2,1); adde(1,3); adde(3,1); adde(1,4); adde(4,1);
    	for (int i=1;i<=Q;i++) {
    		scanf("%d",&q[i]);
    		adde(n+1,q[i]); adde(q[i],n+1);
    		adde(n+2,q[i]); adde(q[i],n+2);
    		n+=2;
    	}
    	init();
    	int now=2,A=2,B=4,m=4;
    	for (int i=1;i<=Q;i++) {
    		int u=q[i],v=++m;
    		int d1=dist(v,A),d2=dist(v,B);
    		if (d1>now) now=d1,B=v;
    		else if (d2>now) now=d2,A=v;
    		v=++m;
    		d1=dist(v,A),d2=dist(v,B);
    		if (d1>now) now=d1,B=v;
    		else if (d2>now) now=d2,A=v;
    		printf("%d
    ",now);
    	}
    	return 0;
    }
    

    Task 6

    第一问就是裸的最大生成树,边权排序跑kruskal就可以了。

    第二问求一棵树上满足对于路径([u,v])上两个编号为(i,j)的点,在满足(i>j)的条件下,最大化(c_i - c_j) 的值。

    考虑到编号对答案的限制,所以我们不妨考虑用倍增来完成限制。

    可以首先用5个倍增数组,分别表示"(u)向上跳(2^j)的节点是谁","(u)向上跳(2^j)的合法最大值","(u)向上跳(2^j)的合法最小值","(u)向上跳(2^j)的合法最大极差(祖先-孙子)","(u)向上跳(2^j)的合法最大极差(孙子-祖先)"

    最后答案一定是由([u,lca]) , ([v,lca]),跨(lca)三个部分组成的,拼凑一下即可。

    复杂度是(O(n log n))

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<queue>
    #include<algorithm>
    #include<stack>
    #define N 60000
    std::queue<int>q; 
    struct aa{
        int p,next;
    }da[N*3];
    struct noz{
        int x,y,len;
    }line[N*2];
    int fa[N],dd,tou[N],mx[N][21],mi[N][21],fm[N][21],dp[N][21],dp2[N][21];
    int n,b[N],m,dep[N];
    bool vis[N];
    int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
    void add(int x,int y){
        da[++dd].p=y;da[dd].next=tou[x];tou[x]=dd;
        da[++dd].p=x;da[dd].next=tou[y];tou[y]=dd;
    }
    void bfs(){
        q.push(1);
        vis[1]=1;
        dep[1]=1;
        while (!q.empty()){
            int u=q.front();q.pop();
            for (int i=tou[u];i;i=da[i].next){
                int v=da[i].p;
                if (!vis[v]){
                    dep[v]=dep[u]+1;
                    q.push(v),vis[v]=1;
                    fm[v][0]=u;
                    mx[v][0]=std::max(b[v],b[u]);
                    mi[v][0]=std::min(b[v],b[u]);
                    dp[v][0]=b[u]-b[v];
                    dp2[v][0]=b[v]-b[u];
                }
            }
            for (int i=1;i<=20;i++){
                fm[u][i]=fm[fm[u][i-1]][i-1];
                if (!fm[u][i])break;
                mx[u][i]=std::max(mx[u][i-1],mx[fm[u][i-1]][i-1]);
                mi[u][i]=std::min(mi[u][i-1],mi[fm[u][i-1]][i-1]);
                dp[u][i]=std::max(dp[u][i-1],mx[fm[u][i-1]][i-1]-mi[u][i-1]);
                dp[u][i]=std::max(dp[u][i],dp[fm[u][i-1]][i-1]);
                dp2[u][i]=std::max(dp2[u][i-1],mx[u][i-1]-mi[fm[u][i-1]][i-1]);
                dp2[u][i]=std::max(dp2[u][i],dp2[fm[u][i-1]][i-1]);
            }
        }
    }
    int lca(int x,int y){
        if (dep[x]<dep[y])std::swap(x,y);
        for (int i=20;i>=0;i--)
          if (dep[x]-dep[y]>=1<<i)x=fm[x][i];
        if (x==y)return x;
        for (int i=20;i>=0;i--)
          if (fm[x][i]!=fm[y][i]){
            x=fm[x][i];
            y=fm[y][i];
          }
        if (x==y)return x;
        else return fm[x][0];
    }
    int getMAX(int x,int goal){
        int ma=0;
        for (int i=20;i>=0;i--)
          if (dep[x]-dep[goal]>=1<<i){
            ma=std::max(ma,mx[x][i]);
            x=fm[x][i];
          }
        return ma;
    } 
    int  getMIN(int x,int goal){
        int ma=0x5f5f5f5f;
        for (int i=20;i>=0;i--)
          if (dep[x]-dep[goal]>=1<<i){
            ma=std::min(ma,mi[x][i]);
            x=fm[x][i];
          }
        return ma;
    }
    int opre1(int x,int goal){
        int ma=0x5f5f5f5f;
        int num=0;
        for (int i=20;i>=0;i--)
         if (dep[x]-dep[goal]>=1<<i){
            num=std::max(num,dp[x][i]);
            num=std::max(num,mx[x][i]-ma);
            ma=std::min(ma,mi[x][i]);
            x=fm[x][i];
        }
        return num;
    }
    int opre2(int x,int goal){
        int ma=-0x5f5f5f5f;
        int num=0;
        for (int i=20;i>=0;i--)
         if (dep[x]-dep[goal]>=1<<i){
            num=std::max(num,dp2[x][i]);
            num=std::max(num,ma-mi[x][i]);
            ma=std::max(ma,mx[x][i]);
            x=fm[x][i];
        }
        return num;
    }
    bool cmp(noz a,noz b){return a.len>b.len;}
    int main(){
        while (scanf("%d",&n)!=EOF){
            dd=0;
            memset(tou,0,sizeof(tou));
            memset(vis,0,sizeof(vis));
            memset(dep,0,sizeof(dep));
            for (int i=1;i<=n;i++)fa[i]=i;
            for (int i=1;i<=n;i++)
              for (int j=0;j<=20;j++)
                mx[i][j]=dp[i][j]=dp2[i][j]=fm[i][j]=0,mi[i][j]=0x5f5f5f5f;
            for (int i=1;i<=n;i++)scanf("%d",&b[i]); 
            scanf("%d",&m);
            for (int i=1;i<=m;i++)scanf("%d%d%d",&line[i].x,&line[i].y,&line[i].len);
            std::sort(line+1,line+1+m,cmp);
            int tot=0;int sum=0;
            for (int i=1;i<=m;i++){
                int fx=find(line[i].x);
                int fy=find(line[i].y);
                if (fx!=fy){
                    fa[fx]=fy;
                    ++tot;
                    sum+=line[i].len;
                    add(line[i].x,line[i].y);
                    if (tot==n-1)break;
                }
            }
            printf("%d
    ",sum);
            bfs();
            int q;
            scanf("%d",&q);
            for (int i=1;i<=q;i++){
                int x,y;
                scanf("%d%d",&x,&y);
                int LCA=lca(x,y);
                int x1=opre1(x,LCA);
                int x2=opre2(y,LCA);
                printf("%d
    ",std::max(std::max(x1,x2),getMAX(y,LCA)-getMIN(x,LCA)));
            }
        }
        return 0;
    }
    
    
  • 相关阅读:
    MVC MVP MVVM
    RC4 对称加密
    ARM 寻址方式
    杂项记录 arm64 的一些特性
    无向图-笔记-代码
    iOS 自定义导航栏
    ios中设置UIButton圆角,添加边框
    iOS 中UICollectionView实现各种视觉效果
    UIScrollView中UITableView
    iOS 13 适配总结
  • 原文地址:https://www.cnblogs.com/ljc20020730/p/11291477.html
Copyright © 2020-2023  润新知