Problem A: 道路建设 解题报告
-
一定存在一个最优解是一条链
否则可以接上去,不会更差
-
边权最小的边一定在这条链上
这个比较显然
可以把所有边都减去这个最后加上就行了
-
把链上的边按距离当前根的深度从小到大排列,设第一个零边位置为\(k\),那么到\(k-2\)及之前所有的边边权非严格递减,这个可以手玩一下。
具体思路就是如果不递减,就可以强行把0边一个端点接过来,换掉\(k-1\),可以证明这样不会变差。
于是边权可以直接累加起来了。
-
建议一个虚点,连接所有的点,边权为被连接点出边最小边权的两倍。
两倍是模拟把0边拉过来耗掉的那条边,然后如果拉的恰好是0边,计算的时候也没有影响。
其余边照连,从虚点跑单源最短路即可。
-
为什么跑出的最短路树边权递增?
特殊点在于先连接的那条最短边,反证一下即可。
Code:
#include <cstdio>
#define ll long long
const int N=2010;
int n,g[N][N];
ll mi=(1ll<<50),dis[N],vis[N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
scanf("%d",&g[i][j]);
mi=mi<g[i][j]?mi:g[i][j];
g[j][i]=g[i][j];
}
for(int i=1;i<=n;i++)
{
int id=0;
for(int j=1;j<=n;j++)
if(i!=j)
{
g[i][j]-=mi;
if(!id||g[i][j]<g[i][id]) id=j;
}
dis[i]=2ll*g[i][id];
}
for(int i=1;i<=n;i++)
{
int id=0;
for(int j=1;j<=n;j++) if(!vis[j]&&(!id||dis[j]<dis[id])) id=j;
vis[id]=1;
for(int j=1;j<=n;j++) dis[j]=dis[j]<dis[id]+g[id][j]?dis[j]:dis[id]+g[id][j];
}
for(int i=1;i<=n;i++) printf("%lld\n",dis[i]+1ll*(n-1)*mi);
return 0;
}
2019.1.2