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