• 树刷题笔记


    • 树的直径
    • 树的中心
    • 树的重心
    • 洛谷P3398(点和路径关系,LCA)
    • 洛谷P4281(枚举,LCA)
    • 洛谷P5588(树状数组,LCA,思维)
    • 洛谷P5536核心城市(树的中心,贪心)
    • 洛谷P1273有线电视网(树形DP,分组背包)
    • HDU6035Colorful tree(思维好题)

    树的直径

    定义:树中最长的链叫直径
    求法:同时维护每个节点到子叶子结点的最长链和次长链,然后答案就是max(longest[i]+second_longest[i])(i=1,2...n)

    int ml[N],sl[N];
    int dfs(int son,int dad)
    {
       for(auto v:eg[son])
          {
                if(v==dad) continue;
                int l=dfs(son,dad);
                if(l>ml[son]) swap(ml[son],l);
                if(l>sl[son]) swap(sl[son],l);
          }   
       ans=max(ans,sl[son]+ml[son]);
       return ml[son]+len(边长);
    }
    

    树的中心

    定义:到所有点的最长距离最小的点叫做树的中心
    一个结论:从起点开始遍历,这颗树的中心一定存在于重链上(同树链刨分)
    求法:先给出一些定义:

    struct Max_node{
          int v,w;
    }sl[N],ml[N];
    

    ml[i]记录到子叶节点的最长路,sl则是次长路,求法如代码

    void dfs1(int x,int y)//预处理Mt1和Mt2的内容
    {
        for(auto mv:eg[x])if(mv.first!=y){
            int v=mv.first,w=mv.second;
            dfs1(v,x);
            Max_node temp(v,ml[v].w+w);
            if(temp.w>ml[x].w) swap(ml[x],temp);
            if(temp.w>sl[x].w) swap(sl[x],temp);
        }
    }
    void dfs2(int x,int y,int len,int Max_up)//遍历更新答案
    {
        int Max=max(Mt1[x].w,Max_up);//计算该节点的延伸出去的最长路径
        if(Max<ans_w) ans_v=x,ans_w=Max;//更新答案
        if(Max_up>Mt1[x].w) return ;//如果最长路径是往上的,说明没必要往下了,理由同上
        for(auto mv:eg[x])if(mv.first==Mt1[x].v) dfs2(mv.first,x,mv.second,max(sl[x].w,Max_up)+mv.second);
    }
    

    树的重心

    定义:使得连接自身最大子树最小的点
    求法:简单随便求

    int siz[N];
    void dfs(int s,int d)
    {
          siz[s]=1;
          int Max=0;
          for(auto v:eg[s])if(v!=d)
          {
                dfs(v,s);
                siz[s]+=siz[v];
                Max=max(siz[v],Max);
          }
          Max=max(n-siz[s],Max);
          if(Max>Ans_w) Ans_w=Max,Ans_v=s;
    }
    

    洛谷P3398仓鼠找Suger

    题意:n节点的树,q次询问,每次给出两条路径(给出路径的方式是给出起点和终点),问这两条是否有公共点。(n,q<=1e5)

    解答:

    一个结论:
    如果俩路径相交,则 ab 的 LCA 在 cd 上或者 cd 的 LCA 在 ab 上
    另一个结论:
    如果一个点在一个路径上,则这个点和另外两个点的LCA分别是这个点和路径的LCA
    此题结束

    //验证点是否在路径上 
    #include<bits/stdc++.h>
    using namespace std;
    #define f(i,a,b) for(register int i=a;i<=b;++i)
    #define ll long long
    #define N 100005
    inline int read(){int x;scanf("%d",&x);return x;}
    vector<int>eg[N];
    int fa[N][25],deep[N];
    void dfs(int s,int d)
    {
    	deep[s]=deep[d]+1;
    	fa[s][0]=d;
    	for(auto v:eg[s])if(v!=d){
    		dfs(v,s);
    	}
    }
    void init(int n)
    {
    	for(int j=1;j<=20;++j)
    		for(int i=1;i<=n;++i)
    			fa[i][j]=fa[fa[i][j-1]][j-1];
    }
    int LCA(int a,int b)
    {
    	if(deep[a]<deep[b]) swap(a,b);
    	for(int j=20;j>=0;--j) if(deep[fa[a][j]]>=deep[b]) a=fa[a][j];
    	if(a==b) return a;
    	for(int j=20;j>=0;--j) if(fa[a][j]!=fa[b][j]) a=fa[a][j],b=fa[b][j];
    	return fa[a][0];
    }
    bool In_Road(int x,int a,int b,int y)
    {
    	if(x==a||x==b||x==y) return true;
    	int l1=LCA(x,a),l2=LCA(x,b);
    	if(l1==x&&l2==y) return true;
    	if(l1==y&&l2==x) return true;
    	return false;
    }
    void solve()
    {
    	int n=read(),q=read();
    	f(i,1,n-1) 
    	{
    		int a=read(),b=read();
    		eg[a].push_back(b);
    		eg[b].push_back(a);
    	}
    	dfs(1,0);
    	init(n);
    	f(i,1,q)
    	{
    		int a=read(),b=read(),c=read(),d=read();
    		int la1=LCA(a,b),la2=LCA(c,d);
    		if(In_Road(la1,c,d,la2)||In_Road(la2,a,b,la1)) printf("Y\n");
    		else printf("N\n");
    	}
    }
    int main()
    {
    	int T=1;
    	while(T--) solve();
    	return 0;	
    }
    

    洛谷P4281紧急集合

    题意:给一颗n节点的树,m次询问,每次给出三个节点a,b,c求一点d,使得a,b,c到d距离总和最小(n,m<5e5)
    解答:在枚举三个节点个各种情况后发现,可以简化成两种情况
    1:两两求LCA后的三个LCA都一样,则这个LCA就是所求的点
    2:求出的三个LCA两个一样,则不一样的那个LCA就是所求的点
    每种情况求三次LCA,复杂度O(mlogn),常数为3

    //验证点是否在路径上 
    #include<bits/stdc++.h>
    using namespace std;
    #define f(i,a,b) for(register int i=a;i<=b;++i)
    #define ll long long
    #define N 500005
    inline int read(){int x;scanf("%d",&x);return x;}
    vector<int>eg[N];
    int fa[N][25],deep[N];
    void dfs(int s,int d)
    {
    	deep[s]=deep[d]+1;
    	fa[s][0]=d;
    	for(auto v:eg[s])if(v!=d){
    		dfs(v,s);
    	}
    }
    void init(int n)
    {
    	for(int j=1;j<=20;++j)
    		for(int i=1;i<=n;++i)
    			fa[i][j]=fa[fa[i][j-1]][j-1];
    }
    int LCA(int a,int b)
    {
    	if(deep[a]<deep[b]) swap(a,b);
    	for(int j=20;j>=0;--j) if(deep[fa[a][j]]>=deep[b]) a=fa[a][j];
    	if(a==b) return a;
    	for(int j=20;j>=0;--j) if(fa[a][j]!=fa[b][j]) a=fa[a][j],b=fa[b][j];
    	return fa[a][0];
    }
    bool In_Road(int x,int a,int b,int y)
    {
    	if(x==a||x==b||x==y) return true;
    	int l1=LCA(x,a),l2=LCA(x,b);
    	if(l1==x&&l2==y) return true;
    	if(l1==y&&l2==x) return true;
    	return false;
    }
    int Abs(int x){
    	if(x<0) return -x;
    	return x;
    }
    void solve()
    {
    	int n=read(),q=read();
    	f(i,1,n-1) 
    	{
    		int a=read(),b=read();
    		eg[a].push_back(b);
    		eg[b].push_back(a);
    	}
    	dfs(1,0);
    	init(n);
    	f(i,1,q)
    	{
    		int a=read(),b=read(),c=read();
    		int la1=LCA(a,b),la2=LCA(b,c),la3=LCA(a,c);
    		if(la1==la2&&la2==la3)
    			printf("%d %d\n",la1,deep[a]+deep[b]+deep[c]-3*deep[la1]);
    		else{
    			if(la1==la2) 
    				printf("%d %d\n",la3,Abs(deep[la3]-deep[a])+Abs(deep[la3]-deep[c])+Abs(deep[la1]-deep[la3])+Abs(deep[la1]-deep[b]));
    			else if(la2==la3) 
    				printf("%d %d\n",la1,Abs(deep[la1]-deep[a])+Abs(deep[la1]-deep[b])+Abs(deep[la1]-deep[la3])+Abs(deep[la2]-deep[c]));
    			else 
    				printf("%d %d\n",la2,Abs(deep[la2]-deep[b])+Abs(deep[la2]-deep[c])+Abs(deep[la1]-deep[la2])+Abs(deep[la1]-deep[a]));
    		}
    	}
    }
    int main()
    {
    	int T=1;
    	while(T--) solve();
    	return 0;	
    }
    

    洛谷P5588小猪佩奇爬树

    题意:给一颗n节点树,每个节点有一个颜色,求对每个颜色有多少条长度大于一的路径包含了所有该颜色
    解答:定义一个cnt[i]数组,记录遍历当前点的时候i颜色出现的次数,假设颜色i的一个“极低点”代表该点颜色为i且的子节点中没有颜色i的点,稍加分析可以得出,对于颜色i
    1:有1个极低点时,颜色i所有点都是极低点的祖先节点,形成一条链,只要记录最高点位置,就能轻易求出路径数
    2:有两个极低点时,如果颜色i的最高点不在两个极低点的路径上时,就形成一条以两个极低点为端点的链,否则路径不存在
    3:极低点大于3时,路径不存在

    PS:只有一个点时要特判

    //验证点是否在路径上 
    #include<bits/stdc++.h>
    using namespace std;
    #define f(i,a,b) for(register int i=a;i<=b;++i)
    #define ll long long
    #define N 1000050
    #define pb push_back
    inline ll read(){ll x;scanf("%lld",&x);return x;}
    vector<ll>eg[N],vex[N],temp;
    ll fa[N][25],deep[N],co[N],cnt[N],Ans[N],hst[N],siz[N];
    void dfs(ll s,ll d)
    {
    	siz[s]=1;
    	deep[s]=deep[d]+1;
    	if(!hst[co[s]]) hst[co[s]]=s;
    	fa[s][0]=d;
    	for(auto v:eg[s])if(v!=d){
    		dfs(v,s);
    		siz[s]+=siz[v];
    	}
    }
    void init(ll n)
    {
    	for(int j=1;j<=20;++j)
    		for(int i=1;i<=n;++i)
    			fa[i][j]=fa[fa[i][j-1]][j-1];
    }
    ll LCA(ll a,ll b)
    {
    	if(deep[a]<deep[b]) swap(a,b);
    	for(int j=20;j>=0;--j) if(deep[fa[a][j]]>=deep[b]) a=fa[a][j];
    	if(a==b) return a;
    	for(int j=20;j>=0;--j) if(fa[a][j]!=fa[b][j]) a=fa[a][j],b=fa[b][j];
    	return fa[a][0];
    }
    bool In_Road(ll x,ll a,ll b,ll y)
    {
    	if(x==a||x==b||x==y) return true;
    	ll l1=LCA(x,a),l2=LCA(x,b);
    	if(l1==x&&l2==y) return true;
    	if(l1==y&&l2==x) return true;
    	return false;
    }
    ll find_fa(ll u,ll v)
    {
    	for(int j=20;j>=0;--j) if(deep[fa[u][j]]>deep[v]) u=fa[u][j];
    	return u;
    }
    void dfs2(ll s,ll d)
    {
    	ll now=cnt[co[s]];
    	for(auto v:eg[s])if(v!=d){
    		dfs2(v,s);
    	}
    	if(now==cnt[co[s]]) vex[co[s]].pb(s);
    	++cnt[co[s]];
    }
    void solve()
    {
    	ll n=read();
    	f(i,1,n) co[i]=read();
    	f(i,1,n-1)
    	{
    		ll a=read(),b=read();
    		eg[a].pb(b);
    		eg[b].pb(a);
    	}
    	dfs(1,0);
    	init(n);
    	dfs2(1,0);
    	f(i,1,n)
    	{
    		if(vex[i].empty()) Ans[i]=(n-1)*n/2;
    		else if(vex[i].size()==1)
    		{
    			ll v=hst[i];
    			if(v==vex[i][0])
    			{
    				temp.clear();
    				for(auto u:eg[v])if(u!=fa[v][0]) temp.pb(u);
    				ll sum=0,sie=temp.size();
    				f(i,0,sie-1) f(j,i+1,sie-1) sum+=siz[temp[i]]*siz[temp[j]];
    				sum+=siz[v]*(n-siz[v]+1)-1;
    				Ans[i]=sum;
    			}
    			else
    			{
    				ll u=find_fa(vex[i][0],v);
    				Ans[i]=siz[vex[i][0]]*(n-siz[u]);
    			}
    		}
    		else if(vex[i].size()==2)
    		{
    			ll u=hst[i];
    			if(!In_Road(u,vex[i][0],vex[i][1],LCA(vex[i][0],vex[i][1]))) continue;
    			Ans[i]=siz[vex[i][0]]*siz[vex[i][1]];
    		}
    		else Ans[i]=0;
    	}
    	f(i,1,n) printf("%lld\n",Ans[i]);
    }
    int main()
    {
    	int T=1;
    	while(T--) solve();
    	return 0;	
    }
    

    洛谷P5536核心城市

    题意:给一个n节点的树,选k个核心点(k个核心点必须在同一个连通块),使得非核心点到最近的核心点最大的距离最小
    解答:[贪心]先求出直径的中点,即中心,可以知道这个点一定在核心点中(假设不在,那最终非核心点距离最近的核心点一定大于L/2(L为直径),与中心是核心点时最大距离为L/2相悖,因此第一个点选这个点准没错)。
    然后,设与中心相邻的点为vi,下一个点就选max(vi|Li最大)(Li代表vi可延伸出去的最大距离)。(证明:如果不选这个点,非核心点到核心点的距离一定大于max(Li),选了有可能会变小,因此选它准没错)

    //验证点是否在路径上 
    #include<bits/stdc++.h>
    using namespace std;
    #define f(i,a,b) for(register int i=a;i<=b;++i)
    #define ll long long
    #define N 100050
    #define pb push_back
    inline int read(){int x;scanf("%d",&x);return x;}
    vector<int>eg[N];
    struct Max_node{
    	int v,w;
    	Max_node(int vv=0,int ww=0):v(vv),w(ww){}
    }ml[N],sl[N];
    int mv[N],vis[N];
    void dfs1(int s,int d)
    {
    	mv[s]=1;
    	for(auto v:eg[s])if(v!=d)
    	{
    		dfs1(v,s);
    		mv[s]=max(mv[v]+1,mv[s]);
    		Max_node temp(v,mv[v]);
    		if(temp.w>ml[s].w) swap(temp,ml[s]);
    		if(temp.w>sl[s].w) swap(temp,sl[s]); 
    	}
    }
    int Ans_v,Ans_w=9999999;
    void dfs2(int s,int up)
    {
    	int Max=0;
    	if(ml[s].w>up) 
    	{
    		if(ml[s].w<Ans_w) Ans_w=ml[s].w,Ans_v=s;
    	} 
    	else
    	{
    		if(up<Ans_w) Ans_w=up,Ans_v=s;return ;
    	}
    	if(ml[s].v) dfs2(ml[s].v,max(up+1,sl[s].w+1));
    }
    int lgst[N];
    void dfs3(int s,int d)
    {
    	lgst[s]=1;
    	for(auto v:eg[s])if(v!=d){
    		dfs3(v,s);
    		lgst[s]=max(lgst[s],lgst[v]+1);
    	}
    }
    struct Node{
    	int v,w;
    	Node(int vv,int ww):v(vv),w(ww){}
    	friend bool operator<(Node a,Node b){
    		return a.w<b.w;
    	}
    };
    priority_queue<Node>qn;
    void solve()
    {
    	int n=read(),k=read();
    	f(i,1,n-1)
    	{
    		int a=read(),b=read();
    		eg[a].pb(b);
    		eg[b].pb(a);
    	}
    	dfs1(1,0);
    	dfs2(1,0);
    	dfs3(Ans_v,0);
    	qn.push(Node(Ans_v,lgst[Ans_v]));
    	while(k--)
    	{
    		int v=qn.top().v;
    		vis[v]=1;
    		qn.pop();
    		for(auto u:eg[v]) if(!vis[u]) qn.push(Node(u,lgst[u]));
    	}
    	printf("%d\n",qn.top().w);
    }
    int main()
    {
    	int T=1;
    	while(T--) solve();
    	return 0;	
    }
    

    洛谷P1273有线电视网

    题意:给一颗n节点有权树(每条边都有个权值),树的每个叶子节点都有一个贡献,当选取几个叶子结点时,会得到的分数为(叶子结点贡献和)-(根节点到这些叶子结点的权值和),问满足分数不为负的情况下,最多能选择多少叶子结点。
    题解:dp[i][j]代表以i为根节点的子树选j个叶子结点得到的最多分数。根节点与子节点形成分组背包,但是有一些细节处理,比如dp数组要初始化为负无穷。

    #include<bits/stdc++.h>
    using namespace std;
    #define f(i,a,b) for(register int i=a;i<=b;++i)
    #define ll long long
    #define N 3505
    #define INF 999999999
    #define pb push_back
    #define mp make_pair
    #define fi first
    #define se second
    inline ll read(){ll x;scanf("%lld",&x);return x;}
    vector<pair<int,int> >eg[N];
    int siz[N],dp[N][N];
    void dfs1(int s,int d)
    {
    	if(siz[s]==1) return ;
    	for(auto pv:eg[s])
    	{
    		int v=pv.fi;
    		dfs1(v,s);
    		siz[s]+=siz[v];
    	}
    }
    void dfs2(int s,int d)
    {
    	for(auto pv:eg[s])
    	{
    		int v=pv.fi,w=pv.se;
    		dfs2(v,s);
    		for(int i=siz[s];i>=0;--i)
    			for(int k=0;k<=siz[v]&&k<=i;++k)
    				dp[s][i]=max(dp[s][i],dp[s][i-k]+dp[v][k]-w);
    	}
    }
    void solve()
    {
    	int n=read(),m=read();
    	f(i,1,n-m)
    	{
    		int k=read();
    		f(j,1,k) eg[i].pb(mp(read(),read()));
    	}
    	f(i,1,n) f(j,1,n) dp[i][j]=-INF;
    	f(i,n-m+1,n) dp[i][1]=read(),siz[i]=1;
    	dfs1(1,0);
    	dfs2(1,0);
    	for(int i=siz[1];i>=0;--i) if(dp[1][i]>=0){
    		cout<<i<<endl;
    		return ;
    	}
    }
    int main()
    {
    	ll T=1;
    	while(T--) solve();
    	return 0;
    } 
    

    HDU6035Colorful tree

    题意:给一颗n节点树,每个节点有一个颜色(范围在1-n),求所有路径经过颜色种类数的和,即求:
    \(\sum_{i=0}^n\)\(\sum_{j=1}^n{Color(i,j)}\)(其中Color(i,j)代表路径ij上的颜色有几种)
    题解:求每条路径的颜色数等价于求每种颜色有几条路径经过的数量和,再将每种颜色经过的路径数转化成每种颜色有几条路径没经过,一次dfs可解。

    #include <bits/stdc++.h>
    using namespace std;
    #define f(i,a,b) for(register int i=a;i<=b;++i)
    #define N 100005
    #define ll long long
    inline ll read(){ll x;scanf("%lld",&x);return x;}
    ll sum[N],color[N],siz[N];
    vector<ll>eg[N];
    ll ans;
    void DFS(ll s,ll d)
    {
    	siz[s]=1;
    	for(ll v:eg[s]) if(v!=d)
    	{
    		ll last = sum[color[s]];
    		DFS(v,s);
    		siz[s]+=siz[v];
    		ll now = sum[color[s]];
    		ll d1=now-last,d2=siz[v]-d1;
    		ans=(ans+d2*(d2-1)/2);
    		sum[color[s]]+=d2; 
    	}
    	sum[color[s]]++;
    }
    int main() 
    {
    	ll n,Cas=0;
        while(~scanf("%lld",&n))
        {
        	++Cas;
        	ans=0;
        	memset(sum,0,sizeof sum);
        	f(i,1,n) color[i]=read(),eg[i].clear();
        	f(i,1,n-1)
        	{
        		ll x=read(),y=read();
        		eg[x].push_back(y);
        		eg[y].push_back(x);
    		}
    		DFS(1,0);
    		f(i,1,n) ans+=(n-sum[i])*(n-sum[i]-1)/2;
    		ans=n*n*(n-1)/2-ans;
    		printf("Case #%lld: %lld\n",Cas,ans);
    	}
        return 0;
    }
    
    
  • 相关阅读:
    线程池execute执行顺序
    三个线程交替打印1到100
    mysql优化
    最大回文子串
    AOP实现日志收集和记录
    LoadingCache缓存使用(LoadingCache)
    springboot项目在idea中实现热部署
    idea破解
    linux常用命令
    Oracle的分条件计数COUNT(我的条件),由浅入深
  • 原文地址:https://www.cnblogs.com/Tiork/p/14203246.html
Copyright © 2020-2023  润新知