文章标题 UVALive 6062:Reduce the Maintenance Cost(双联通分量缩点)

本文介绍了一种通过Tarjan算法缩点和树形DP的方法来解决图论问题,即如何在考虑每条边的特殊维护成本的情况下,分配成本以使所有顶点的最大负担最小化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值