社交树 题解
题目id:20316
题目描述
有一句话叫作:“最多只需要\(7\)个人就可以认识任何人”,因为每个人都有自己的朋友,而朋友也有自己的朋友,所以在这世界上的任何一个陌生人,可能都是你朋友的朋友的朋友的朋友的朋友的朋友的朋友。当然这并不意味着我们和任何一个陌生人建立联系很容易,通过朋友来找朋友往往需要花费不小的代价。现在你只有一个朋友,但你的朋友却有很多他的朋友(朋友是相互的,你也是他的朋友)。你和朋友进行联络需要付出一定的代价,同样的你朋友联络他的朋友也有一定的代价。给你所有的人和每个人的朋友关系,求出你联络他们每个人所需要的代价。
解题思路
本题解法较多,本文仅介绍最短路算法和DFS算法。
1.最短路算法
虽然这是一颗树,但我们还是可以用图的最短路算法解决此题。
假设\(\left(x,y\right)\)是一对朋友,他们联系对方花费的代价为\(p_x\)和\(p_y\),那我们可以建两条有向边分别连接对方,权值分别为\(p_y\)和\(p_x\)。
然后跑一遍最短路就行了,由于题目求的是根节点到各个节点的距离,因此该题可以用单源最短路径过(我们班还有个用\(Floyd\)过的)
本蒟蒻忘了\(dijkstra\)算法板子,只好写了个\(SPFA\)献丑(别说我凡尔赛真的不会!):
如果不太了解最短路,可以看一下OI Wiki的介绍。
void spfa()
{
queue<int>q;
q.push(1);
vis[1]=1;
memset(dis,127,sizeof(dis));
dis[1]=a[1];
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=0;i<v[u].size();++i)
if(dis[u]+v[u][i].second<dis[v[u][i].first])
{
dis[v[u][i].first]=dis[u]+v[u][i].second;
if(!vis[v[u][i].first])
{
q.push(v[u][i].first);
vis[v[u][i].first]=1;
}
}
}
}
这里需要注意的是,\(dis_1\)一定要赋值\(a_1\)!我们班有个大聪明赋的\(1\)从\(\textcolor[RGB]{82,196,26}{AC}\)到全\(\textcolor[RGB]{231,76,60}{WA}\),白白丢了\(100\)分(他写的是DFS)。
最短路算法 AC Code
#include<bits/stdc++.h>
#define N 1000007
#define INF 1e18
#define MOD 1000000007
#define LL long long
#define pb push_back
#define lb long double
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define IOS ios::sync_with_stdio(0),cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
LL n,a[5005],dis[5005];
bool vis[5005];
vector<pair<LL,LL>>v[5005];
int main()
{
IOS;
cin>>n;
for(int i=1;i<=n;++i)
cin>>a[i];
for(int i=1,x,y;i<n;++i)
{
cin>>x>>y;
v[x].pb({y,a[y]});
v[y].pb({x,a[x]});
}
queue<LL>q;
q.push(1);
vis[1]=1;
memset(dis,127,sizeof(dis));
dis[1]=a[1];//千万记得赋值a[1]!
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=0;i<v[u].size();++i)
if(dis[u]+v[u][i].second<dis[v[u][i].first])
{
dis[v[u][i].first]=dis[u]+v[u][i].second;
if(!vis[v[u][i].first])
{
q.push(v[u][i].first);
vis[v[u][i].first]=1;
}
}
}
for(int i=1;i<=n;++i)
cout<<dis[i]<<' ';
return 0;
}
2.DFS算法
由于题目保证是一颗树,所以任意两个点只有唯一一条路径,故我们也可以使用DFS求路径长度。
还是用邻接表建边,DFS每个点。
如果已经走到目的地了,直接输出当前答案即可(我当图做取了最小值)
如果没走到,则走到下一个点,将答案增加两人联络的代价。
由于是树所以可以不回溯(我当图做顺手回溯了只有\(\textcolor[RGB]{52,152,219}{TLE}80pts\))
DFS算法 80pts Code
#include<bits/stdc++.h>
#define N 1000007
#define INF 1e18
#define MOD 1000000007
#define LL long long
#define pb push_back
#define lb long double
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define IOS ios::sync_with_stdio(0),cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
LL n,a[5005],minn=LONG_LONG_MAX;
bool vis[5005];//可以不用
vector<LL>v[5005];
void dfs(LL k,LL x,LL cnt)
{
if(k==x)
{
minn=min(minn,cnt);
return;
}
for(int i=0;i<v[k].size();++i)
if(!vis[v[k][i]])
{
vis[v[k][i]]=1;
dfs(v[k][i],x,cnt+a[v[k][i]]);
vis[v[k][i]]=0;//这里回溯了所以超时
}
}
int main()
{
IOS;
cin>>n;
for(int i=1;i<=n;++i)
cin>>a[i];
for(int i=1,x,y;i<n;++i)
{
cin>>x>>y;
v[x].pb(y);
v[y].pb(x);
}
cout<<a[1]<<' ';
for(int i=2;i<=n;++i)
{
memset(vis,0,sizeof(vis));
minn=LONG_LONG_MAX;
dfs(1,i,a[1]);
cout<<minn<<' ';
}
return 0;
}
DFS算法 AC Code
#include<bits/stdc++.h>
#define N 1000007
#define INF 1e18
#define MOD 1000000007
#define LL long long
#define pb push_back
#define lb long double
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define IOS ios::sync_with_stdio(0),cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
LL n,a[5005],minn=LONG_LONG_MAX;
vector<LL>v[5005];
bool vis[5005];
void dfs(LL k,LL x,LL cnt)
{
if(minn<cnt)return;
if(k==x)
{
minn=min(minn,cnt);
return;
}
for(int i=0;i<v[k].size();++i)
if(!vis[v[k][i]])
vis[v[k][i]]=1,dfs(v[k][i],x,cnt+a[v[k][i]]);
}
int main()
{
IOS;
cin>>n;
for(int i=1;i<=n;++i)
cin>>a[i];
for(int i=1,x,y;i<n;++i)
{
cin>>x>>y;
v[x].pb(y);
v[y].pb(x);
}
cout<<a[1]<<' ';
for(int i=2;i<=n;++i)
{
memset(vis,0,sizeof(vis));
vis[1]=1;
minn=LONG_LONG_MAX;
dfs(1,i,a[1]);
cout<<minn<<' ';
}
return 0;
}