「JOISC2016I-電報」题解

I - 電報

state

给出 \(N\) 个点,每个点的出度均为 \(1\),给出这 \(N\) 个点初始指向的点 \(A_i\),和改变这个点指向的目标所需要的价值 \(C_i\)

求让所有点强连通的最小花费。

对于全部的数据,\(1 \leq N \leq 10^{5}\) ,\(1 \leq A_i \leq N\) , \(A_i \neq i\) , $1 \leq C_i \leq 10^{9} $。

sol

每个点均只会有一条出边,这显然是个外向基环树森林。

那么最后产生的强连通分量只会是一个环,这是显然的。

我们先从一棵基环树考虑:

最后状态中,每个点入度只为 \(1\),因此所有入度多于 \(1\) 的点,其多的入度必然被移走。不难发现,移的先后顺序不影响,我们其实就是要把环外的部分全部改成链,因此事实上只需要按边权从小到大移边即可,被移走的边可以指向任意一个入度为 \(0\) 的点。

但是,倘若环内的每条边都被保留,那么在保证被移走的边指向另一个入度为零的点的时候,事实上分裂出了两个环,此后再把这两个独立的环合并是不优的,因为我们至少要移动两个连通块内各一条边。而假如我们保证,环上至少有一条边被断开,那么就可以少一次移动,显然不劣。

也就是说,对于一个基环树,我们只需要枚举其环上断开的边即可,产生的贡献变化是易得的,只需要判断多断了一条边的那个点是否可以多保留一条原本被删除的边即可。值得注意的是,对于一个环,事实上是不需要额外断边的,需要特判一下。

然后考虑森林。

在产生环之后再去连边事实上是不优的,合并 \(n\) 个环时事实上只需要每个环移动一条最优的边即可。然而,事实上,对于每一棵基环树(特判一下环,环还是要移动一条最小的边权才能破开),破环的时候把那条边甩到别的连通块上就能顺便合并了。

也就是说,事实上最后的答案就是,每棵基环树必须破开环的情况下的最小代价与每个环最小边权之和。

记得特判整个图只有一个环的情况,然后就没了。

code

const int N=1e5+5;

int n;
int a[N];
ll c[N];
ll ans;
vec<int> g[N];
vec<ll> d[N];

bool vis0[N],huan[N],vis1[N];
vec<int> path;
int dfs1(int now){
    int res=0;vis1[now]=1;
    if(d[now].empty())return now;
    for(auto nxt:g[now]){
        if(vis1[nxt])continue;
        if(res=dfs1(nxt))return res;
    }
    return res;
}
bool dfs0(int now){
    vis0[now]=1;path.pub(now);
    int nxt=a[now];
    if(!vis0[nxt]){
        if(dfs0(nxt))return 1;
    }else{
        while(1){
            int bk=path.back();path.pob();
            huan[bk]=1;
            if(bk==nxt)return 1;
        }
    }
    path.pob();
    return 0;
}

ll mn,mj;
bool vis[N];
bool all;
void dfs(int now){
    if(!huan[now])all=0;
    vis[now]=1;chmin(mn,c[now]);
    sort(d[now].begin(),d[now].end());
    int id=d[now].size();
    repl(i,0,id-1)ans+=d[now][i];
    for(auto nxt:g[now])if(!vis[nxt])dfs(nxt);
    if(huan[now]){
        if(d[a[now]].back()==c[now])chmin(mj,c[now]-(d[a[now]].size()>1?d[a[now]][d[a[now]].size()-2]:0));
        else chmin(mj,0ll);
    }
}

inline void Main(){
    read(n);
    rep(i,1,n)read(a[i],c[i]),g[i].pub(a[i]),g[a[i]].pub(i),d[a[i]].pub(c[i]);
    int cnt=0;
    rep(i,1,n)if(!vis[i]){
        int be=dfs1(i);
        dfs0(be?be:i);
        mn=mj=INF;all=1;
        dfs(i);
        if(!all)ans+=mj;
        else ans+=mn;
        ++cnt;
    }
    if(cnt==1&&all)ans-=mn;
    put(ans);
}
posted @ 2025-07-11 18:35  LastKismet  阅读(38)  评论(0)    收藏  举报