双连通分量
边双连通分量+DP (其实不用DP,直接建树+遍历一次就能计算出全部的DP值)
题意无向图连通,所以只要从一个点运行一次dfs即可,在运行dfs过程中保存下所有的桥并且计算出所有的边双连通分量。在tarjan之后对原图进行缩点,缩点后就能得到一棵,树边刚好就是全部的桥。缩点后每个大点都有一个权值,权值等于 = 属于该连通分量的每个小点的权值和。
因此保存下全部桥是为了方便建树。建树之后对树进行一次遍历(很多人说是DP,其实不算是DP,只是简单的遍历而已)。遍历过程要计算每个节点的dp值,dp[i] = 以点i为节点的子树的所有节点的权值和
因此每计算完一个节点的dp值后,就可以看看切断这条树边,能不能更新最大的答案, 切断该边的结果为 (SUM - dp[i]) - dp[i]
#include <iostream> #include <cstdio> #include <cstring> #include <utility> #include <vector> #include <stack> using namespace std; #define N 10010 #define M 20010 #define INF 0x3f3f3f3f int n,tot,val[N]; int head[N],dfn[N],low[N],belong[N],ins[N],dcnt,bcnt; int weight[N],dp[N],vis[N],SUM,res; typedef pair<int ,int> pii; struct edge { int u,v,used,next; }e[2*M]; vector<pii>bridge; stack<int>sta; vector<int>ver[N]; void add(int u, int v, int k) { e[k].u = u; e[k].v = v; e[k].used = 0; e[k].next = head[u]; head[u] = k++; u = u^v; v = u^v; u = u^v; e[k].u = u; e[k].v = v; e[k].used = 0; e[k].next = head[u]; head[u] = k++; } void dfs(int u , int fa) { dfn[u] = low[u] = ++dcnt; sta.push(u); ins[u] = 1; for(int k=head[u]; k!=-1; k=e[k].next) if(!e[k].used) { e[k].used = e[k^1].used = 1; int v = e[k].v; if(!dfn[v]) //树边 { dfs(v,u); low[u] = min(low[u] , low[v]); if(low[v] > dfn[u]) //桥 { bridge.push_back(make_pair(u,v)); while(true) { int x = sta.top(); sta.pop(); ins[x] = 0; belong[x] = bcnt; if(x == v) break; } bcnt++; //统计连通分支数 } } else if(ins[v]) //后向边 low[u] = min(low[u] , dfn[v]); } } inline int abs(int x ,int y) { return x>y? x-y : y-x ; } void travel(int u) //遍历整个树,顺便计算出dp值,并且顺便计算出答案 { vis[u] = 1; dp[u] = weight[u]; for(int i=0; i<ver[u].size(); i++) { int v = ver[u][i]; if(vis[v]) continue; travel(v); dp[u] += dp[v]; //加上子树的dp值 } res = min(res , abs(SUM-2*dp[u])); } void build() //缩点后建树,树边就是桥 { SUM = 0; res = INF; for(int i=0; i<bcnt; i++) { weight[i] = vis[i] = dp[i] = 0; ver[i].clear(); } //计算缩点后每个点的权值 for(int i=0; i<n; i++) weight[belong[i]] += val[i]; for(int i=0; i<bcnt; i++) SUM += weight[i]; for(int i=0; i<bridge.size(); i++) { int u = belong[ bridge[i].first ]; int v = belong[ bridge[i].second ]; ver[u].push_back(v); ver[v].push_back(u); } travel(0); // for(int i=0; i<n; i++) printf("%d[%d]\n",i,belong[i]); // for(int i=0; i<bcnt; i++) printf("w=%d\n",weight[i]); // for(int i=0; i<bcnt; i++) printf("dp=%d\n",dp[i]); } void solve() { dcnt = bcnt = 0; bridge.clear(); while(!sta.empty()) sta.pop(); memset(ins,0,sizeof(ins)); memset(dfn,0,sizeof(dfn)); dfs(0,-1); while(!sta.empty()) { int x = sta.top(); sta.pop(); ins[x] = 0; belong[x] = bcnt; } bcnt++; if(bcnt == 1) //整个图就是个边双连通分支,不存在桥 { cout << "impossible" << endl; return ; } build(); //建树,并且遍历,遍历过程中就能计算出答案 cout << res << endl; } int main() { while(cin >> n >> tot) { tot *= 2; memset(head,-1,sizeof(head)); for(int i=0; i<n; i++) cin >> val[i]; for(int i=0; i<tot; i+=2) { int u,v; cin >> u >> v; add(u,v,i); } solve(); } return 0; }