比赛总结
题目
题意:
一个有n个城市m条道路的图,如果一条边删除后,有些城市变得不可达,则它需要维护,花费为L×D,L是道路长度,D是变得不可达的城市对数。每条道路维护花费由连接的城市中的一个承担。城市本来也有固定的维护花费。求所有可能中,花费最大的城市的最小花费。
题解:
首先可以用tarjan求桥,如果(u,v)之间为桥,且u为父亲,则回溯时已遍历的点数-dfn[v]+1就得到和v在同一双连通分量的城市数。
然后由于桥的性质,将所有非桥边删去后图就变成了森林。对于每一棵树可以单独求最小的最大可能花费:
二分答案,然后遍历树,对于当前点而言,如果任意儿子的子树不能将边全部维护,则这答案不可行;
其次,每条连向儿子的边尽可能让儿子维护,否则父亲维护,如果当前点不能维护所有的这样的边,答案不可行;
看看当前点是否能再维护到父亲的边。
//Time:552ms //Length:2742B #include <cstdio> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define MAXN 40010 #define MOD 1000000007 int he[MAXN],to[MAXN],nex[MAXN],len[MAXN],pri[MAXN],top; int dfn[MAXN],low[MAXN],num,sta[MAXN],stop,cnt[MAXN]; bool is[MAXN],vi[MAXN],cou[MAXN][2]; void add(int u,int v,int w) { to[top]=v; len[top]=w; nex[top]=he[u]; he[u]=top++; } void tarjan(int u,int pre) { dfn[u] = low[u] = num++; sta[stop++]=u; for(int i = he[u]; i != -1; i = nex[i]) { int v = to[i]; if(v==pre) continue; if(dfn[v]==-1) { tarjan(v,u); if(low[v]>dfn[u]) { is[i^1] = is[i]=1; cnt[i^1]=cnt[i]=num-dfn[v]; } low[u]=min(low[u],low[v]); } else low[u] = min(low[u],dfn[v]); } } bool dfs(int h,long long l,long long lim) { long long sum=pri[h]; cou[h][0]=cou[h][1]=0; vi[h]=1; for(int i=he[h];i!=-1;i=nex[i]) if(is[i]&&!vi[to[i]]) { long long tmp=(long long)len[i]*cnt[i]*(stop-cnt[i]); if(!dfs(to[i],tmp,lim)) return false; if(!cou[to[i]][0]) return false; if(!cou[to[i]][1]) sum+=tmp; } if(sum<=lim) cou[h][0]=1; else return false; if(sum+l<=lim) cou[h][1]=1; return true; } bool check(long long lim) { for(int i=0;i<stop;++i) vi[sta[i]]=0; for(int i=0;i<stop;++i) if(!vi[sta[i]]) if(!dfs(sta[i],0,lim)) return false; return true; } int main() { //freopen("/home/moor/Code/input","r",stdin); int cas,n,m; long long ans,l,r,mid; scanf("%d",&cas); for(int hh=1;hh<=cas;++hh) { scanf("%d%d",&n,&m); l=0; for(int i=1;i<=n;++i) scanf("%d",&pri[i]),l=max(l,(long long)pri[i]); memset(he,-1,sizeof(he)); memset(dfn,-1,sizeof(dfn)); memset(is,0,sizeof(is)); memset(vi,0,sizeof(vi)); memset(cou,0,sizeof(cou)); top=0; for(int i=0;i<m;++i) { int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,c); } ans=l; for(int i=1;i<=n;++i) if(dfn[i]==-1) { num=0; stop=0; tarjan(i,-1); l=ans; r=1e15; while(l<r) { mid=(l+r)/2; if(check(mid)) r=mid; else l=mid+1; } ans=max(ans,l); } printf("Case %d: ",hh); cout<<ans<<'\n'; } return 0; }