• 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);
    }
    
  • 相关阅读:
    3dsmaxunity3d
    libav android移植交叉编译
    libav 由显卡中读取数据制作视频
    超详细mysql left join,right join,inner join用法分析
    VC常见入门问题总结
    c#皮肤美化
    论坛
    vc中文件的读写操作
    MySQL的mysqldump工具的基本用法
    perl产生随机数
  • 原文地址:https://www.cnblogs.com/cnyzz/p/16144616.html
Copyright © 2020-2023  润新知