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
*/