题意:给你一个n个点的坐标,之间两两相连,其中2~n-1形成的边中有一条边不能用,问你最小生成树的最大值。
想法:当对原图求最小生成树之后,记录这一棵树,当我们删除一条边之后,最小生成树就分成了A,B两个连通分量,则需要保存当删除这条边是A到B的最短距离。
小技巧:现在的问题就是:删除一边,求A到B的最短距离?
最笨的方法肯定太耗时间了,就是枚举每一条边然后跑spfa那么时间复杂度就是O(edges^3),但是通过树形dp,枚举每一个点那么时间复杂度O(n^2),又是因为edges=n^2。把枚举的点当成是root点,也就是规定有root的A到B一定是root->B中的点最短。dp[u][v]表示删除uv这条边两个连通块的最短距离。
还有一点要注意的是与root相连的点是不可以更新的,因为root->v是要删除的最小生成树的边,而要找的是除它以外的边。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define inf 0x7fffffff
using namespace std;
int n,k;
double map[1000+50][1000+50],dp[1000+50][1000+50];
int used[1000+50][1000+50],pre[1000+50];
double ans;
struct node
{
int x,y;
}dir[1000+50];
struct nodee
{
int v,next;
}e[2000+50];
int head[1000+50],cnt;
void add(int a,int b)
{
e[cnt].v=b;
e[cnt].next=head[a];
head[a]=cnt++;
}
void Input()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&dir[i].x,&dir[i].y);
}
}
double Max(double a,double b)
{
if(a>b) return a;
else return b;
}
double Min(double a,double b)
{
if(a<b) return a;
else return b;
}
double dis(node a,node b)
{
double k=sqrt((double)(a.x-b.x)*(double)(a.x-b.x)+(double)(a.y-b.y)*(double)(a.y-b.y));
return k;
}
void build_map()
{
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
{
map[i][j]=map[j][i]=dis(dir[i],dir[j]);
dp[i][j]=dp[j][i]=(double)inf;
}
}
}
void prime()
{
int vis[1000+5];
double low[1000+5];
for(int i=1;i<=n;i++)
{
low[i]=map[1][i];
pre[i]=1;
vis[i]=0;
}
low[1]=0;
vis[1]=1;
pre[1]=-1;
for(int i=2;i<=n;i++)
{
double min=inf;
int pos;
for(int j=1;j<=n;j++)
{
if(!vis[j]&&min>low[j])
{
min=low[j];
pos=j;
}
}
vis[pos]=1;
ans+=low[pos];
used[pre[pos]][pos]=used[pos][pre[pos]]=1;
add(pre[pos],pos);add(pos,pre[pos]);
for(int j=1;j<=n;j++)
{
if(!vis[j]&&map[pos][j]<low[j])
{
low[j]=map[pos][j];
pre[j]=pos;
}
}
}
}
double dfs(int root,int u,int fa)
{
double res=(double)inf;
for(int i=head[u];i+1;i=e[i].next)
{
int v=e[i].v;
if(v==fa) continue;
double tmp=dfs(root,v,u);
dp[u][v]=dp[v][u]=Min(tmp,dp[u][v]);
res=Min(res,tmp);
}
if(root!=fa)
{
res=Min(map[root][u],res);
}
return res;
}
void treatment()
{
memset(used,0,sizeof(used));
memset(head,-1,sizeof(head));
cnt=0;
ans=0;
prime();
double Ans=ans;
for(int i=1;i<=n;i++)
{
dfs(i,i,-1);
}
for(int i=2;i<n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(used[i][j])
{
Ans=Max(Ans,ans+dp[i][j]-map[i][j]);
}
}
}
printf("%.2lf\n",Ans*k);
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
Input();
build_map();
treatment();
}
return 0;
}
本文探讨了一种算法问题——给定一组点的坐标,在这些点间形成最小生成树后,找出删除某条特定边后生成树的最大可能值。文章详细介绍了如何利用树形DP方法高效解决此问题。

869

被折叠的 条评论
为什么被折叠?



