zoukankan      html  css  js  c++  java
  • 【暑假集训模拟DAY8】树上问题

    前言

    树上问题以为还算了解

    但现在看来确实缺的也很多,包括换根DP,树的重心,环套树等等,都不是特别熟悉

    期望:0+30+50+0=80pts

    实际:0+10+50+0=60pts

    中间打疫苗中断也有点影响(回来就有点不想写题了)

    T2标准的暴力分是20,然而我又写了一些特判的部分分(比如判链,判两点相同),反而把暴力搞炸了...

    题解

    T1 reform

    理解倒是不难,却感觉无从下手

    对于每一个点,如果原来不是重心,那么考虑切掉一个大小>n/2的部分的一部分(好像有点绕)

    规定1号点为根,那么对于节点u切掉的部分可能在u子树里,也可能在u子树外,需要分别求出

    如果在u子树里,比较好求出能切掉的最大的但是大小不超过n/2的真子树,同时为了下一步求出子树外的最大部分,也要记录子树内能切掉的次大部分

    对于子树外的最大部分,有3种转移方式,具体见代码

    还有这题用memset清空会TLE,测试了一下觉得是因为memset每次会把N大小的数组全部清空,如果T过大就会TLE;如果循环到n清空,由于n的和不会太大,所以不会TLE

    总之测试数据多的时候慎用memset

    代码:

    点击查看代码
    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int INF = 0x3f3f3f3f,N = 4e5+10;
    int head[N<<1],ecnt=-1;
    int ins[N][2],outs[N];
    int siz[N],n,T;
    //inside,outside
    //ins[u][0]表示最大子树,ins[u][1]表示次大子树 
    void init()
    {
    	memset(head,-1,sizeof(head));
    	ecnt=-1;
    }
    struct edge
    {
    	int nxt,to;
    }a[N<<1];
    void add(int x,int y)
    {
    	a[++ecnt].nxt=head[x];
    	a[ecnt].to=y;
    	head[x]=ecnt;
    }
    void dfs1(int u,int fa)//这里是回溯的时候转移ins 
    {
    	siz[u]=1;
    	for(int i=head[u];~i;i=a[i].nxt)
    	{
    		int v=a[i].to;
    		if(v==fa) continue;
    		dfs1(v,u);
    		siz[u]+=siz[v];
    		int tmp;
    		if(siz[v]<=n/2) tmp=siz[v];
    		else tmp=ins[v][0];
    		if(ins[u][0]<=tmp) ins[u][1]=ins[u][0],ins[u][0]=tmp;
    		else if(ins[u][1]<=tmp) ins[u][1]=tmp;
    	}//更新需要维护的2个信息:子树内最大值、次大值,为下面求子树外最大值做准备 
    }
    void dfs2(int u,int fa)//这里是从上往下的时候转移outs 
    {
    	for(int i=head[u];~i;i=a[i].nxt)
    	{
    		int v=a[i].to;
    		if(v==fa) continue;
    		//outs[v]有三个转移方式:子树内最大值,子树内次大值,子树外最大值(均为u的) 
    		if(n-siz[v]<=n/2) outs[v]=n-siz[v];
    		else outs[v]=outs[u];
    		if(ins[v][0]==ins[u][0]||siz[v]==ins[u][0]) outs[v]=max(outs[v],ins[u][1]);
    		else outs[v]=max(outs[v],ins[u][0]); 
    		dfs2(v,u);
    	}
    }
    
    int main()
    {
    	scanf("%d",&T);
    	while(T--)
    	{
    		scanf("%d",&n);
    		//init();
    		ecnt=-1;
    		for(int i=1;i<=n;i++) outs[i]=ins[i][1]=ins[i][0]=siz[i]=0,head[i]=head[i+n]=-1;
    		for(int i=1;i<n;i++)
    		{
    			int u,v;
    			scanf("%d%d",&u,&v);
    			add(u,v),add(v,u);
    		}
    		dfs1(1,-1);
    		dfs2(1,-1);
    		for(int i=1;i<=n;i++)
    		{
    			bool flag=0;
    			//printf("i=%d
    ",i);
    			if(n-siz[i]<=n/2)
    			{
    				for(int j=head[i];~j;j=a[j].nxt)
    				{
    					int v=a[j].to;
    					if(siz[i]>=siz[v]&&siz[v]-ins[v][0]>n/2)//写下标的时候都要仔细思考 
    						flag=1,printf("0 ");
    				}
    				if(!flag) printf("1 ");
    			}
    			else 
    			{
    				if(n-siz[i]-outs[i]>n/2) printf("0 ");
    				else printf("1 ");
    			}
    		}
    		printf("
    ");
    	}
    	return 0;
    }
    /*
    2
    3
    1 2
    2 3
    5
    1 2
    1 3
    1 4
    1 5
    */
    

    T2 build

    题意:给出一棵树上两个点,求出sigma(树上所有点到这两点中的较小距离)

    暴力过不了:因为多次询问,每次询问都要重新对每个点找到最短距离,复杂度O(nmlogn)(或者大概O(nm),一时间居然忘了我考场怎么写的暴力了...)

    正解:对于每个点DP求出子树内和子树外所有点到根的深度之和,倍增找到两个给定点的中点,就可以把树划分成两部分,两部分答案分别是到两个点的距离之和

    注意:这里也是用到两次dfs,分别在从下往上回溯的时候和从上往下的时候转移DP

    代码:

    点击查看代码
    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int INF = 0x3f3f3f3f;
    const int N=1e5+100;
    int n,m;
    int u,v,x,y;
    struct node{
    	int to,nxt;
    }a[N<<1];
    int head[N],ecnt=-1;
    void add(int x,int y){
    	a[++ecnt]=(node){y,head[x]};
    	head[x]=ecnt;
    }
    int dep[N];
    ll dis[N],up[N],siz[N],f[N][22];
    void dfs1(int u,int fa)
    {
    	siz[u]=1;
    	for(int i=1;i<=20;i++) f[u][i]=f[f[u][i-1]][i-1];
    	for(int i=head[u];~i;i=a[i].nxt)
    	{
    		int v=a[i].to;
    		
    		if(v==fa) continue;
    		dep[v]=dep[u]+1;
    		f[v][0]=u;
    		dfs1(v,u);
    		siz[u]+=siz[v];
    		dis[u]+=dis[v]+siz[v];
    	}
    }
    void dfs2(int u,int fa)
    {
    	for(int i=head[u];~i;i=a[i].nxt)
    	{
    		int v=a[i].to;
    		if(v==fa) continue;
    		up[v]=up[u]+dis[u]-dis[v]-siz[v]+n-siz[v]; 
    		dfs2(v,u);
    	}
    }
    int lca(int x,int y)
    {
    	if(dep[x]<dep[y]) swap(x,y);
    	for(int i=20;i>=0;i--)
    	{
    		if(f[x][i]&&dep[f[x][i]]>=dep[y]) x=f[x][i];
    	}
    	if(x==y) return x;
    	for(int i=20;i>=0;i--)
    	{
    		if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    	}
    	return f[x][0];
    }
    inline int Dis(int x,int y)
    {
    	return dep[x]+dep[y]-2*dep[lca(x,y)];
    }
    void getmid(){
    	int anc=lca(u,v);
    	int len=Dis(u,v);
    	int s=len/2;if(len&1==0) s--;
    	if(dep[u]<dep[v]) swap(u,v);
    	x=u;
    	for(int k=20;k>=0;k--)
    	{
    		if(s<1<<k) continue; 
    		x=f[x][k],s-=(1<<k);
    	}	
    	y=f[x][0];
    }
    int main()
    {
    	memset(head,-1,sizeof(head));
    	scanf("%d",&n);
    	for(int i=1;i<n;i++)
    	{
    		scanf("%d%d",&x,&y);
    		add(x,y);add(y,x);
    	}
    	dfs1(1,0);
    	dfs2(1,0);
    	scanf("%d",&m);
    	for(int i=1;i<=m;i++){
    		scanf("%d%d",&u,&v);
    		getmid();
    		ll ans=0;
    		if(dep[x]<dep[y]) swap(x,y);
    		ans+=dis[u]+up[u]-up[x]-(n-siz[x])*Dis(x,u);
    		ans+=dis[v]+up[v]-dis[x]-(siz[x])*Dis(x,v);
    		//printf("u=%lld v=%lld
    ",dis[u]+up[u]-up[x]-(n-siz[x])*Dis(x,u),dis[v]+up[v]-dis[x]-(siz[x])*Dis(x,v));
    		//printf("dis[u]=%d,dis[v]=%d,up[u]=%d,up[v]=%d
    ",dis[u],dis[v],up[u],up[v]);
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    Selenium WebDriver 中鼠标和键盘事件分析及扩展
    Windows Mobile 常用键值VK对应表
    application.yml
    示例
    秒懂HTTPS接口(实现篇)
    秒懂HTTPS接口(接口测试篇)
    秒懂HTTPS接口(原理篇)
    SpringBoot全局捕获异常示例
    官方文档
    Python基础02——控制流
  • 原文地址:https://www.cnblogs.com/conprour/p/15154882.html
Copyright © 2011-2022 走看看