文章標題 UVALive 6062:Reduce the Maintenance Cost(雙聯通分量縮點)

Reduce the Maintenance Cost

題意:在有n(n <= 10000)個點的無向圖上,定義有m條邊,每條邊有自己的長度L,還有一個維護值val=N*L,其中N的定義是
N=破壞掉這條邊時有多少點對不連通。
每條邊的val值需要連接這條邊兩個點中的一個點來承擔,現在每個點有一個初始值,問怎樣分配使得所有點中最大的值最小。
分析:可以知道的是,只有橋纔有val值,其他不是橋的邊的val值都是0,因爲沒有點對會由於這條邊被刪除而不連通,所以我們可以通過tarjan縮點然後重新建樹(森林),這樣新建的樹(森林)中的邊就是橋的,而橋可以通過樹形DP來求出每個子樹的節點數。
代碼:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <set>
#include <map>
#include <algorithm>
#include <math.h>
#include <vector>
using namespace std;
typedef long long ll;

const int mod=1e9+7;
const int maxn=1e5+10;

ll a[maxn];
int n,m;
ll mid;

struct Edge{
    int from,to,nex;
    ll w;
    bool cnt;//是否爲橋 
}edge[maxn];
int head[maxn],tot;
int Low[maxn],dfn[maxn],st[maxn],belong[maxn];
int idx,top;
int block; //聯通塊的數目 
bool Instack[maxn];
int bridge;//橋的數目 
int num[maxn];

void init(){
    tot=0;memset (head,-1,sizeof (head));
    memset (dfn,0,sizeof (dfn));
    memset (Instack,false,sizeof (Instack));
    memset (num,0,sizeof (num));
    idx=top=block=bridge=0;
}
void addedge(int u,int v,int w){
    edge[tot]=Edge{u,v,head[u],w,0};
    head[u]=tot++;
}

void Tarjan(int u,int pre){
    Low[u]=dfn[u]=++idx;
    st[top++]=u;
    Instack[u]=true;
    for (int i=head[u];i!=-1;i=edge[i].nex){
        int v=edge[i].to;
        if (v==pre)continue;
        if (!dfn[v]){
            Tarjan(v,u);
            if (Low[u]>Low[v])Low[u]=Low[v];
            if (Low[v]>dfn[u]){
                bridge++;edge[i].cnt=true;edge[i^1].cnt=true;
            }
        }else if (Instack[v]&&Low[u]>dfn[v]){
            Low[u]=dfn[v];
        }
    }
    if (Low[u]==dfn[u]){
        block++;
        int v;
        do{
            v=st[--top];
            Instack[v]=false;
            belong[v]=block;
            num[block]++;
        }while (v!=u);
    }
}

struct node {//重新建圖用的節點 
    int u,v,from,to,nex;//from表示的是這條邊在原來的圖上連接的兩個節點,下面用來+val值用的 
    ll d;
}e[maxn];
int first[maxn];
void add(int u,int v,int from,int to,ll d){
    e[tot]=node{u,v,from,to,first[u],d};
    first[u]=tot++; 
}

int sz[maxn],fa_id[maxn];//fa_id[u]用來標記第幾條邊 
void dfs(int u,int fa){//計算每個節點的孩子數 
    sz[u]=num[u];fa_id[u]=-1;
    for (int i=first[u];i!=-1;i=e[i].nex){
        int v=e[i].v;
        if (v==fa){
            fa_id[u]=i;
            continue;
        }
        dfs(v,u);
        sz[u]+=sz[v];
    }
} 
//計算每條邊的權值
int vis[maxn]; 
void dfs2(int u,int fa,int treenode){//treenode樹的節點數 
    vis[u]=1;
    for (int i=first[u];i!=-1;i=e[i].nex){
        int v=e[i].v;
        if (v==fa){
            e[i].d*=(ll)sz[u]*(ll)(treenode-sz[u]);
            e[i^1].d=e[i].d;
            continue;
        }
        dfs2(v,u,treenode);
    }
}
void work(){//計算每個節點的孩子數目,和每條橋的價值
    memset (sz,0,sizeof (sz));
    for (int i=1;i<=block;i++){
        if (sz[i]==0)dfs(i,-1);
    } 
    memset (vis,0,sizeof (vis));
    for (int i=1;i<=block;i++){
        if (vis[i]==0)dfs2(i,-1,sz[i]);
    } 
}

ll dp[maxn];
bool DFS(int u,int fa){
    vis[u]=1;
    for (int i=first[u];i!=-1;i=e[i].nex){
        int v=e[i].v;
        if (v==fa)continue;
        if (!DFS(v,u))return false;
    }
    if (fa_id[u]!=-1){
        int oldfrom=e[fa_id[u]].from,oldto=e[fa_id[u]].to;
        ll val=e[fa_id[u]].d;
        if (dp[oldfrom]+val<=mid)dp[oldfrom]+=val;//儘可能加在葉子節點 
        else if (dp[oldto]+val<=mid)dp[oldto]+=val;
        else return false;
    }
    return dp[u]<=mid;
}
bool ok(){
    for (int i=1;i<=n;i++)dp[i]=a[i]; 
    memset (vis,0,sizeof (vis));
    for (int i=1;i<=block;i++){
        if (vis[i]==0){
            if (!DFS(i,-1))return false;
        }
    }
    return true;
}

int main()
{
    int T;
    scanf ("%d",&T);
    int cas=1;
    while (T--){
        init();
        scanf ("%d%d",&n,&m); 
        ll hi=(ll)1e18;
        ll lo=0;
        for (int i=1;i<=n;i++){
            scanf ("%lld",&a[i]);
            lo=max((ll)a[i],lo);
        }
        int u,v,w;
        for (int i=0;i<m;i++){
            scanf ("%d%d%d",&u,&v,&w);
            addedge(u,v,w);addedge(v,u,w);
        }
        for (int i=1;i<=n;i++)if (!dfn[i])Tarjan(i,-1);
        int tot2=tot;
        memset (first,-1,sizeof (first));tot=0;
        for (int i=0;i<tot2;i+=2){
            int u=edge[i].from,v=edge[i].to,w=edge[i].w;
            if (belong[u]==belong[v])continue;
            add(belong[u],belong[v],u,v,w);//重新建圖 
            add(belong[v],belong[u],v,u,w);
        }
        work();//計算每個節點的孩子數目,和每條橋的價值
        ll ans=hi+1;
        while (lo<=hi){
            mid=(lo+hi)/2;
            if (ok()){
                hi=mid-1;
                ans=min(ans,mid); 
            }else lo=mid+1; 
        }
        printf ("Case %d: %lld\n",cas++,ans);
    }
    return 0;
}
/*
3
2 1 
5 10 
1 2 10
6 6 
10 20 30 40 50 60 
1 2 1 
2 3 1 
1 3 1 
1 4 6 
1 5 6 
4 6 2
3 1 
10 20 30 
2 3 10
*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章