对于一个有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;
}
打代码的时候有几个要注意的问题:
- last[ ] 记得赋-1;
- v[] 每次都要清成true;
- 记得把自己的重量加上(即加一)
好了再看一题:
有一棵二叉树,每个节点上保存一个值,需要找到一个节点使得每个点到它的长度乘上那个点的值的和最小。
这题即是叫我们求带权树的重心。(带权树:点上有权,重心:树若以某点为根,使得该树最大子树的结点数最小,那么这个点则为该树的重心,一棵树可能有多个重心。)
先讲一下重心的性质:
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;
}