树和树的性质和题

对于一个有n个点,n-1条边的图,且保证每个点两两可以到达,我们称之为树。(推荐别看我的博客学
树有许多的性质,无环,有根,有直径和长度等。
有一题 :

在 W 星球上有 n 个国家。为了各自国家的经济发展,他们决定在各个国家之间建设双向道路使得国家之间连通。但是每个国家的国王都很吝啬,他们只愿意修建恰好 n−1 条双向道路。

每条道路的修建都要付出一定的费用,这个费用等于道路长度乘以道路两端 的国家个数之差的绝对值。由于国家的数量十分庞大,道路的建造方案有很多种,同时每种方案的修建费用难以用人工计算,国王们决定找人设计一个软件,对于给定的建造方案,计算出所需要的费用。请你帮助国王们设计一个这样的软件。(洛谷P2052)。

对于此题我们先分析题目,发现其实就是求出一条路径两边的节点数,然后因为一共只有n个节点,于是若一边有x个节点的话那另一边就有n-x个节点。
也就说我们只需要求出每条路径一边的节点数即可。然后我们再来看,我们路径的一端?那可以传化为一个节点包括自己的节点数量即是边一边的节点数。再想想,我们可以任意找一个点,从它开始求出每个节点包括的节点数(我先统计不包括自己节点的然后最后再加上自己),然后便利每一条路径用n-x和x求出每一条的费用。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
int ans=0;	
struct pp
{
	int x,y,c,next;
};
pp p[4000000];
int f[4000000];
int len=0;
int last[4000000];
bool v[4000000];
void ins(int x,int y,int c)
{
	len++;
	p[len]={x,y,c,last[x]};
	last[x]=len;
	return ;	
}
void dfs(int x)
{
	v[x]=false;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;
		if(v[y]==true)
		{
			f[x]++;
			dfs(y);
			f[x]+=f[y];
		}
	}
	return ;
}
void dfs2(int x)
{
	v[x]=false;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;
		if(v[y]==true)
		{	
			ans+=p[i].c*abs((f[y]+1)-(n-f[y]-1));
			dfs2(y);
		}
	}
	return ;
}
signed main()
{
	memset(v,true,sizeof(v));
	memset(f,0,sizeof(f));
	memset(last,-1,sizeof(last));
	scanf("%lld",&n);
	for(int i=1;i<=n-1;i++) 	
	{
		int x,y,c;	
		scanf("%lld%lld%lld",&x,&y,&c);
		ins(x,y,c);
		ins(y,x,c);
	}
	dfs(1);
	memset(v,true,sizeof(v));
	dfs2(1);
	printf("%lld",ans);
	return 0; 
 } 

打代码的时候有几个要注意的问题:

  1. last[ ] 记得赋-1;
  2. v[] 每次都要清成true;
  3. 记得把自己的重量加上(即加一)

好了再看一题:

有一棵二叉树,每个节点上保存一个值,需要找到一个节点使得每个点到它的长度乘上那个点的值的和最小。

这题即是叫我们求带权树的重心。(带权树:点上有权,重心:树若以某点为根,使得该树最大子树的结点数最小,那么这个点则为该树的重心,一棵树可能有多个重心。)

先讲一下重心的性质:

1、树上所有的点到树的重心的距离之和是最短的,如果有多个重心,那么总距离相等。
2、插入或删除一个点,树的重心的位置最多移动一个单位。
3、若添加一条边连接2棵树,那么新树的重心一定在原来两棵树的重心的路径上。

那怎么求出树的重心呢?

其实就和上题差不多,我们设有一个数组 f[] 保存每个点到根的距离(就是每个点到1的距离乘上它的权值), s[]表示的是每个点的加上它儿子的权值,sum保存所有点的权值和。
然后就可以像上一题一样处理,不过不同的是我们每次要减去s[y]的值加上(sum-s[y])就行了。

#include<bits/stdc++.h>
using namespace std;
int n,m;
int len=0;
int a[101001];
int last[101010];
int s[101001];
int f[101010];
int sum=0;
int ans=1e9;
bool v[101011];
struct pp
{
	int x,y,next;
};
pp p[101001];
void ins(int x,int y)
{
	len++;
	p[len]={x,y,last[x]};
	last[x]=len;
	return ;
}
void dfs(int x,int k)
{
	v[x]=false;
	f[1]=f[1]+k*s[x];
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;
		if(v[y]==true)
		{
			dfs(y,k+1);
			s[x]+=s[y];
		}
	}
	return ;
}
void dfs2(int x)
{
	v[x]=false;
	ans=min(ans,f[x]);
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;
		if(v[y]==true) 
		{
			f[y]=f[x]-s[y]+(sum-s[y]);
			dfs2(y);
		}
	}
	return ;
}
int main()
{
	memset(last,-1,sizeof(last));
	memset(v,true,sizeof(v));
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x,y;
		scanf("%d%d%d",&s[i],&x,&y);
		if(x>0) ins(i,x);
		if(y>0) ins(i,y); 
		sum+=s[i];
	}
	dfs(1,0);
	memset(v,true,sizeof(v));
//	printf("%d ",f[1]);
	dfs2(1);
	printf("%d",ans); 
	return 0;
}
 

不得不再写些什么了。
嗯…树的话有很多种结构,给出一题增加一下对树的理解P1185 绘制二叉树 对于这题一眼就看出模拟,可怎么模拟?可以考虑

#include<bits/stdc++.h>
using namespace std;
int n,m,k,p,f[901][1600];
char c[901][1600];
void dfs(int x,int y,int a,int b,int k,int xx,int yy)
{
	if(x==n) 
	{
		c[x][y]='o';
		return ;
	}
	if(k==1)
	{
		c[x][y]='o';
		int X=xx+1,Y=(yy-1)*2+1;
		if(f[X][Y]!=1) dfs(x+1,y-1,a+1,b,2,X,Y);
		X=xx+1,Y=yy*2;
		if(f[X][Y]!=1) dfs(x+1,y+1,a+1,b,3,X,Y);
	}
	else 
	{
		if(k==2)
		{
			c[x][y]='/';	
			if(a*2==b) dfs(x+1,y-1,1,a,1,xx,yy);
			else dfs(x+1,y-1,a+1,b,2,xx,yy);
		}
		else 
		{
			c[x][y]=92;
			if(a*2==b) dfs(x+1,y+1,1,a,1,xx,yy);
			else dfs(x+1,y+1,a+1,b,3,xx,yy);
		}
	}
}
void make()	
{
	n=3;m=6*(1<<(k-2))-1;
	for(int i=3;i<=k;i++) n*=2;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++) c[i][j]=' ';
	}
	dfs(1,m/2+1,1,n,1,1,1);
	return ;
}
int main()
{
	scanf("%d%d",&k,&p);
	while(p--)
	{
		int x,y;scanf("%d%d",&x,&y);
		f[x][y]=1;
	}
	if(k==1) n=m=1,c[1][1]='o';
	else make();
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++) printf("%c",c[i][j]);
		printf("\n");
	}
	return 0;
}

有一道树上的dp,其实就是P5628 【AFOI-19】面基正常的容斥罢了,只不过没怎么做过导致看不出来。


//考虑树上的容斥,具体说就是通过dp进行转移设f[i][j] 第i个节点影响距离为j时影响的边重要度之和。
//分两步,一是统计子树,直接从[son,j-1]转移即可,第二是统计父亲,那么就是从[fa,j-1]转移
//但是,发现父亲会重复计算一些值,那么我们减去[x,j-2],然后再加上父亲的值就可以,至于这一步为什么要这样容斥?
//你会发现所有距离父亲j-1的点包括了儿子对吧,考虑如何删除,那么直接通过自己删就行(注意这个自己是没统计父亲的时候的) 
//先算出边权后计算儿子的值,最后父亲 
#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,k,len=1,last[2000001],f[40000][300],siz[1000001];
struct pp
{
	int x,y,c,next;
};pp p[2000001];
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,0,last[x]};last[x]=now;
	return ;
}
void dfs1(int x,int fa)
{
	siz[x]=1;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		dfs1(y,x);p[i].c=p[i^1].c=siz[y]*(n-siz[y]);
		siz[x]+=siz[y];
	}
	return ;
}
void dfs2(int x,int fa)
{
	f[x][0]=0;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		dfs2(y,x);
		for(int j=1;j<=k+1;j++) f[x][j]+=p[i].c+f[y][j-1];
	}
	return ; 
}
void dfs3(int x,int fa,int val)
{
	if(x!=1)
	{
		for(int i=k+1;i>=2;i--) f[x][i]=(f[x][i]+f[fa][i-1]-f[x][i-2]);
		f[x][1]+=val;	
	}
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		dfs3(y,x,p[i].c);
	}
	return ;
}
signed main()
{
	memset(last,-1,sizeof(last));memset(f,0,sizeof(f));
	scanf("%lld%lld",&n,&k);
	for(int i=1;i<=n-1;i++)
	{
		int x,y;scanf("%lld%lld",&x,&y);
		ins(x,y);ins(y,x);
	}
	dfs1(1,0);dfs2(1,0);dfs3(1,0,0);
	int ans=0;
	for(int i=1;i<=n;i++) ans=max(ans,f[i][k+1]);
	printf("%lld",ans);
	return 0;
} 

Clearing Up一道比较有意思的树论,与其想成立,不如想不成立,首先可以先根据奇偶性判掉奇数的部分,然后考虑如何处理sm之间的关系,考虑先不停的加入s的边,但不形成环,如果不够的话输出-1,然后考虑加入m,如果加入m(不能成环)之后无法构成一棵树,那么也-1。那么同样也对m做一遍。贴一下别人的做法:
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int n,mm,len1=0,len2=0,lasts[100001],lastm[100001],pd=1;
int fa[100001],v[100001],ans[100001];
struct pp
{
	int x,y,num;
};pp s[100001],m[100001];
void ins(int x,int y,int num,char op[])
{
	if(op[1]=='S') s[++len1]={x,y,num};
	else  m[++len2]={x,y,num};
	return ;
}
int findfa(int x)
{
	if(fa[x]==x) return x;
	return fa[x]=findfa(fa[x]);
}
int main()
{
	memset(v,0,sizeof(v));memset(ans,0,sizeof(ans));
	scanf("%d%d",&n,&mm);
	if((n-1)%2==1) pd=-1;
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=mm;i++) 
	{
		int x,y;char op[10];scanf("%d%d%s",&x,&y,op+1);
		ins(x,y,i,op);
	}
	int cnt1=0,cnt2=0;
	for(int i=1;i<=len1;i++) 
	{
		int x=s[i].x,y=s[i].y,fx=findfa(x),fy=findfa(y);
		if(fx!=fy) cnt1++,fa[fx]=fy;
	}
	if(cnt1<(n-1)/2) pd=-1;
	for(int i=1;i<=len2;i++)
	{
		int x=m[i].x,y=m[i].y,fx=findfa(x),fy=findfa(y);
		if(fx!=fy) v[i]=1,cnt2++,fa[fx]=fy;
	}
	if(cnt1+cnt2<n-1) pd=-1;//printf("*");
	for(int i=1;i<=n;i++) fa[i]=i;//printf("%d %d\n",len1,len2);
	for(int i=1;i<=len2;i++)
	{
		if(v[i])
		{
			int x=m[i].x,y=m[i].y,fx=findfa(x),fy=findfa(y);
			if(fx!=fy) fa[fx]=fy,ans[m[i].num]=1;
		}
	}
	for(int i=1;i<=len2&&cnt2<(n-1)/2;i++)
	{
		if(!v[i])
		{
			int x=m[i].x,y=m[i].y,fx=findfa(x),fy=findfa(y);
			if(fx!=fy) cnt2++,fa[fx]=fy,ans[m[i].num]=1; 
		}
	}
	if(cnt2<(n-1)/2) pd=-1;//printf("***");
	for(int i=1;i<=len1;i++)
	{
		int x=s[i].x,y=s[i].y,fx=findfa(x),fy=findfa(y);
		if(fx!=fy) fa[fx]=fy,ans[s[i].num]=1;
	}
	if(pd==-1) printf("-1");
	else 
	{
		printf("%d\n",n-1);
		for(int i=1;i<=mm;i++)
		{
			if(ans[i]) printf("%d ",i);
		}
	}
	return 0;
}

P1268 树的重量呃一道有思维难度的题目,考虑若是之前的树已经建好了,新加入一条边,那么有多少种方案呢,就是,有i-1种嘛,因为它是找一条边插入进去,那么再考虑其贡献,不妨直接观察其到1的距离之后减去1到其插入的边的长度,这样说可能不太直观。
在这里插入图片描述
那么可以找出距离之后直接枚举最小值即可。

#include<bits/stdc++.h>
using namespace std;
int n,m,ans=0,dis[40][40];
int main()
{
	while(scanf("%d",&n))
	{
		if(!n) break ;ans=0;
		for(int i=1;i<=n-1;i++)
		{
			for(int j=i+1;j<=n;j++) scanf("%d",&dis[i][j]);
		}
		ans+=dis[1][2];
		for(int i=3;i<=n;i++)
		{
			int tmp=1e9;
			for(int j=2;j<=i-1;j++) tmp=min(tmp,dis[1][i]+dis[j][i]-dis[1][j]);
			ans+=(tmp/2);
		}
		printf("%d\n",ans);
	}	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
算法有兴趣的可以来看看 在自然数,且所有的数不大于30000的范围内讨论一个问题:现在已知n条线段,把端点依次输入告诉你,然后有m个询问,每个询问输入一个点,要求这个点在多少条线段上出现过; 最基本的解法当然就是读一个点,就把所有线段比一下,看看在不在线段中; 每次询问都要把n条线段查一次,那么m次询问,就要运算m*n次,复杂度就是O(m*n) 这道题m和n都是30000,那么计算量达到了10^9;而计算机1秒的计算量大约是10^8的数量级,所以这种方法无论怎么优化都是超时 因为n条线段是固定的,所以某种程度上说每次都把n条线段查一遍有大量的重复和浪费; 线段树就是可以解决这类问题的数据结构 举例说明:已知线段[2,5] [4,6] [0,7];求点2,4,7分别出现了多少次 在[0,7]区间上建立一棵满二叉树:(为了和已知线段区别,用【】表示线段树中的线段) 【0,7】 / \ 【0,3】 【4,7】 / \ / \ 【0,1】 【2,3】 【4,5】 【6,7】 / \ / \ / \ / \ 【0,0】 【1,1】 【2,2】 【3,3】 【4,4】 【5,5】 【6,6】 【7,7】 每个节点用结构体: struct line { int left,right; // 左端点、右端点 int n; // 记录这条线段出现了多少次,默认为0 }a[16]; 和堆类似,满二叉树的性质决定a[i]的左儿子是a[2*i]、右儿子是a[2*i+1]; 然后对于已知的线段依次进行插入操作: 从树根开始调用递归函数insert // 要插入的线段的左端点和右端点、以及当前线段树中的某条线段 void insert(int s,int t,int step)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值