[USACO21DEC]Connecting Two Barns S题解

简化题意:

N N N 个点和 M M M 条双向边,你可以至多加两条边,使得 1 1 1 号点和 N N N 号点联通,对于 i i i j j j 两点,加边的代价为 ( i − j ) 2 (i-j)^2 (ij)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 1 号连通块的一个点或 N N N 号连通块的一个点进行加边,此时仅需加 1 1 1 条边即可。
    2. 我们选择 2 − N 2-N 2N 中的一个连通块,将该联通块的头与 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 个连通块的最短距离。

注意:

错误思路:

Wrong

正确思路:

True

实现:

使用并查集与二分查找实现。

时间复杂度 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值