Solution -「keyence2019_e 」Connecting Cities

Source: KEYENCE Programming Contest 2019 E. Connecting Cities

题意:给定一张 \(n\) 个点的完全图,\((i,j)\) 这条边的边权是 \(|i-j|\times D+a_i+a_j\),求这张图的最小生成树。

\(1\le n\le 10^5\)\(1\le D,a_i\le 10^9\)

Solution

考虑到边数较多,任何与边数 \(m\) 相关的最小生成树算法都会挂掉,使用 Boruvka 算法。

那么主要问题就变成了,如何求出一个连通块向外可以连出的最小边,这提示我们去维护边权。

考虑去掉绝对值,钦定 \(i<j\) 或者 \(i>j\),不失一般性的,我们讨论 \(i<j\),此时式子变成 \(i\times D+a_i-j\times D+a_j\)
注意到我们还需要满足 \(i,j\) 不在一个连通块,这里将同一个连通块内的点一起维护,你可以理解为作为树状数组的一个下标,那么满足 \(i,j\) 不在一个连通块就等同于在查询的时候查询 \(j\) 对应连通块的补集,拍到区间上对应一个前缀加上一个后缀。
那么,使用树状数组维护前缀后缀 \(\min\),后缀可以翻转为前缀,这里的 \(\min\)\(i\times D+a_i\),就可以在 \(n\log n\) 的时间内求出最小边权。

套用 Boruvka 即可,时间复杂度 \(O(n\log ^2 n)\)

Code

省略了码头,完整代码可在 Link 查看。

const int N=2e5;
int n,d,a[N+10],cnt,x[N+10],y[N+10],fa[N+10];
ll ans;
int c1[N+10],c2[N+10],mi[N+10];
ll F(int u,int op) {return u?1ll*op*u*d+a[u]:1e18;}
ll H(int u,int v) {return u&&v?1ll*abs(u-v)*d+a[u]+a[v]:1e18;}
void add(int *c,int x,int v,int op) {
    for(int i=x;i<=n;i+=i&-i) if(F(c[i],op)>F(v,op)) c[i]=v;
}
int query(int *c,int x,int op) {
    int ans=0;
    for(int i=x;i;i-=i&-i) if(F(ans,op)>F(c[i],op)) ans=c[i];
    return ans;
}
int find(int u) {
    if(fa[u]==u) return u;
    return fa[u]=find(fa[u]);
}
void solve() {
    memset(c1,0,sizeof c1),memset(c2,0,sizeof c2);
    memset(mi,0,sizeof mi),memset(x,0,sizeof x),memset(y,0,sizeof y);
    FOR(i,1,n) {
        int u=find(i);
        int x=query(c1,u-1,-1),y=query(c2,n-u,-1);
        if(F(x,-1)>F(y,-1)) swap(x,y);
        if(H(x,i)<H(mi[i],i)) mi[i]=x;
        add(c1,u,i,-1),add(c2,n-u+1,i,-1);
    }
    memset(c1,0,sizeof c1),memset(c2,0,sizeof c2);
    ROF(i,n,1) {
        int u=find(i);
        int x=query(c1,u-1,1),y=query(c2,n-u,1);
        if(F(x,1)>F(y,1)) swap(x,y);
        if(H(x,i)<H(mi[i],i)) mi[i]=x;
        add(c1,u,i,1),add(c2,n-u+1,i, 1);
    }
    FOR(i,1,n) {
        int u=find(i);
        if(H(mi[i],i)<H(x[u],y[u])) x[u]=mi[i],y[u]=i;
    }
    FOR(i,1,n) if(find(i)==i) {
        int fx=find(x[i]),fy=find(y[i]);
        if(fx==fy) continue;
        ans+=H(x[i],y[i]),fa[fx]=fy,cnt--;
    }
}
int main() {
    scanf("%d %d",&n,&d);
    FOR(i,1,n) scanf("%d",&a[i]);cnt=n;
    iota(fa+1,fa+n+1,1);
    while(cnt>1) solve();
    printf("%lld\n",ans);
}
posted @ 2022-04-14 15:01  cnyz  阅读(50)  评论(0编辑  收藏  举报