1011 Jumping Monkey
思路:
首先看当前图中剩下的权值最大的点u,要使得答案最优从任何一个节点开始跳,跳到最后肯定会跳到u。那么考虑把u从图中去掉,图会变成若干个连通块,这些连通块自己内部求最优解再加上跳到权值最大的点u就是正解。按照这个思路,每趟将所有连通块中权值最大的点拿出来,对于节点v,如果该节点在第k趟被拿出来,那么节点v的解为k。
这里我们反向进行如上过程,对于最后一趟拿出来的点一定是每个连通块中最小的节点,我们按照权值从小到大遍历每个节点v,并将已经遍历过的并且和v联通的节点标记值w加1,每个节点初始标记值w为1,对节点u来说标记值w的含义为每趟将所有连通块中最大的点拿出来,节点u会在第w次拿出来。也就是反向进行了开始讲的过程。如下图:
复杂度分析:
- 对于节点从小到大按照权值排序O(nlogn)
- 循环遍历每个节点i,对于和节点i联通的所有节点标记值w+1 O(n^2)
题目给的n的范围为1e5明显是复杂度太大了,下面我们优化一下
优化
//先给出find函数
int find(int x)
{
if(x==fa[x])
return x;
else
{
return fa[x]=find(fa[x]);
}
}
按照权值排序排完,先构造一个空的连通图,我们按照从小到大的顺序不断向连通图加入节点v,当前连通图中的连通块如果在原图中有一条边能将连通块和v相连,那么就将v和这个连通块权值最大的点相连,遍历完每个点之后从权值最大的那个点开始跑bfs求每个节点的深度,每个点的深度就是对应的答案,其中根节点的深度为1.正常搜索的话还是n^2的复杂度,这里可以用并查集存来存储每个连通块当前权值最大的点,对于每个点v,在原图中v一步能到达的所有节点求出来,然后看看这些节点在不在新建的连通图中,在的话就并查集求这个节点对应的连通块中权值最大的点,然后添加一条v指向该连通块中权值最大的点的边,并维护并查集。让我们模拟一下,首先排序排完之后,我们加入第一个点,让第一个节点独自构成一个连通块;加入第二个点,假设第二个点和第一个点相连,那么先查询第一个节点所在连通块的权值最大的点,也就是tx=find(第一个节点),看看tx是不是和第二个节点相同,不相同就把tx和第二个节点连一条边(第二个节点为起点,tx为终点,下面也都是这样,新加入的节点为起点,相连连通块中最大的点为终点),并fa[tx]=第二个节点,现在没有其他点和第二个点相连了;加入第三个点,假设第一个点和第二个点都和第三个点相连,tx1=find(第一个节点),看看是否tx1=第三个节点,不相等的话tx1与第三个节点连一条边,fa[tx1]=第三个节点,tx2=find(第二个节点),看看是否tx2=第三个节点,不相等的话tx2与第三个节点连一条边,并且fa[tx2]=第三个节点。接下来的节点按照这个策略遍历完。最后求结果,以权值最大的节点为根节点,并且根节点深度为1,求新构造的图的深度,对应的深度就是每个节点的解。
复杂度分析
图中n个节点n-1条边,对于每个节点将它相邻的节点拿出来,最多有2n个节点,因为所有节点度数总和为2n,也就是最多有2*n次并查集的查询,路径压缩并查集查询复杂度为O(Mlog1+M/NN)其中M为查询次数,N为节点数,所以优化后复杂度为O(nlogn)
代码
#include<bits/stdc++.h>
using namespace std;
int T,n;//节点数n
int head[100005],net[300005],v[300005],d[100005],w[100006],vis[100005];//原图,其中w为权值,d为深度
int tot=0,tot2;//原图边的数目tot1,新的连通图变得个数tot2
int fa[100005];
int head2[100005],net2[300005],v2[300005];//新的连通图
void add(int x,int y)//构造原图
{
v[++tot]=y,net[tot]=head[x],head[x]=tot;
}
void add2(int x,int y)//构造新的连通图
{
v2[++tot2]=y,net2[tot2]=head2[x],head2[x]=tot2;
}
void init()//初始化
{
tot=0;
tot2=0;
for(int i=1;i<=n;i++)
{
d[i]=0,head[i]=0,vis[i]=0,head2[i]=0,fa[i]=i,vis[i]=0;
}
}
int find(int x)//并查集查询节点x对应连通块中权值最大的点
{
if(x==fa[x])
return x;
else
{
return fa[x]=find(fa[x]);
}
}
struct node
{
int p,w;//p为节点编号,w为节点权值
const bool operator<(node y)const//按照权值从小到大排序
{
return this->w<y.w;
}
}que[100005];
void bfs(int x)
{
vis[x]=1;
for(int i=head[x];i;i=net[i])//在原图中找到x点一步能到达的点
{
int y=v[i];
if(!vis[y])//这个点不在新构造的连通图中
continue;
int ty=find(y);//查询这个节点对应连通块中权值最大的点
if(ty==x)//如果这个连通块中权值最大的点不是x就令fa[ty]=x,并且在x和ty之间添加有向边e(x->ty)
continue;
add2(x,ty);
fa[ty]=x;
}
}
void bfs2(int x)//以x节点为根节点在新构造的连通图中求每个节点的深度
{
queue<int>q;
q.push(x);
d[x]=1;//根节点深度为1
while(q.size())
{
x=q.front();
q.pop();
for(int i=head2[x];i;i=net2[i])
{
int y=v2[i];
if(d[y])
continue;
d[y]=d[x]+1;
q.push(y);
}
}
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
init();
for(int i=1;i<n;i++)//输入原图
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
for(int i=1;i<=n;i++)//输入每个点的权值
{
scanf("%d",&w[i]);
que[i].p=i;
que[i].w=w[i];
}
sort(que+1,que+1+n);//对每个点按照权值大小从小到大排序
for(int i=1;i<=n;i++)//排序后从小到大将每个节点加入新构造的连通图
{
bfs(que[i].p);
}
bfs2(que[n].p);//以权值最大的节点为根节点求每个节点的深度
for(int i=1;i<=n;i++)//输出每个节点的深度,也就是解
printf("%d\n",d[i]);
}
return 0;
}