简化题意:
有 N N N 个点和 M M M 条双向边,你可以至多加两条边,使得 1 1 1 号点和 N N N 号点联通,对于 i i i 和 j j j 两点,加边的代价为 ( i − j ) 2 (i-j)^2 (i−j)2 。
思路:
对于题目给出的 N N N 个点和 M M M 条边的无向图,我们可以将其视作若干个连通块,而目标是让点 1 1 1 所处的联通块(即为 1 1 1 号连通块)和点 N N N 所处的连通块(即为 N N N 号连通块)联通。
考虑以下情况:
-
点 1 1 1 和点 N N N 已经联通
- 那么此时无需考虑加边,答案为 0 0 0 。
-
点 1 1 1 和点 N N N 尚未联通
- 我们可以选择 1 1 1 号连通块的一个点或 N N N 号连通块的一个点进行加边,此时仅需加 1 1 1 条边即可。
- 我们选择 2 − N 2-N 2−N 中的一个连通块,将该联通块的头与 1 1 1 号连通块的尾连接,将该连通块的尾与 N N N 号联通块的头连接,此时需要加 2 2 2 条边。
小结:不难发现其实情况一是情况二的特例。
综上所述,我们用两个数组 f f f 和 g g g 记录第 i i i 个连通块与第一个连通块的最短距离和与第 N N N 个连通块的最短距离,然后相加即可得到第 i i i 个连通块联通第一个连通块和第 N N N 个连通块的最短距离。
注意:
错误思路:
正确思路:
实现:
使用并查集与二分查找实现。
时间复杂度 O ( n log n ) O(n\log n) O(nlogn) 。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll t,n,m,u,v,fa[1001000],ans,a[1001000],b[1001000],x,y;
ll cnt1,cnt2,f[201000],g[201000],tmp1,tmp2,p1,p2;
ll find(ll x)
{
if(fa[x]==x) return x;
else return fa[x]=find(fa[x]);
}
void uni(ll x,ll y)
{
x=find(x),y=find(y);
fa[x]=y;
}
ll dis(ll x,ll y)
{
return (x-y)*(x-y);
}
int main()
{
cin>>t;
while(t--)
{
ans=LONG_LONG_MAX;
cnt1=cnt2=0;
cin>>n>>m;
for(int i=1;i<=n;i++) fa[i]=i,f[i]=g[i]=LONG_LONG_MAX;
for(int i=1;i<=m;i++)
{
cin>>u>>v;
uni(u,v);
}
x=find(1),y=find(n);
if(x==y)
{
cout<<0<<endl;
}
else
{
for(int i=1;i<=n;i++)
{
if(find(i)==x) a[++cnt1]=i;
if(find(i)==y) b[++cnt2]=i;
}
for(int i=1;i<=n;i++)
{
tmp1=LONG_LONG_MAX;
p1=lower_bound(a+1,a+1+cnt1,i)-a;
if(p1<=cnt1) tmp1=min(tmp1,dis(a[p1],i));
if(p1>1) tmp1=min(tmp1,dis(a[p1-1],i));
f[find(i)]=min(f[find(i)],tmp1);
}
for(int i=1;i<=n;i++)
{
tmp2=LONG_LONG_MAX;
p2=lower_bound(b+1,b+1+cnt2,i)-b;
if(p2<=cnt2) tmp2=min(tmp2,dis(b[p2],i));
if(p2>1) tmp2=min(tmp2,dis(b[p2-1],i));
g[find(i)]=min(g[find(i)],tmp2);
}
for(int i=1;i<=n;i++)
{
if(find(i)==i) ans=min(ans,f[i]+g[i]);
}
cout<<ans<<endl;
}
}
return 0;
}