HDU 2242
一个炎热的下午,Lele照例在教室睡觉的时候,竟然做起了空调教室的美梦。
Lele梦到学校某天终于大发慈悲给某个教室安上了一个空调。而且建造了了M条通气管道,让整个教学楼的全部教室都直接或间接和空调教室连通上,构成了教室群,于是,全部教室都能吹到空调了。
不仅仅这样,学校发现教室人数越来越多,单单一个空调已经不能满足大家的需求。于是,学校决定封闭掉一条通气管道,把全部教室分成两个连通的教室群,再在那个没有空调的教室群里添置一个空调。
当然,为了让效果更好,学校想让这两个教室群里的学生人数尽量平衡。于是学校找到了你,问你封闭哪条通气管道,使得两个教室群的人数尽量平衡,并且输出人数差值的绝对值。
Input
本题目包含多组数据,请处理到文件结束。
每组测试第一行包含两个整数N和M(0<N<=10000,0<M<20000)。其中N表示教室的数目(教室编号从0到N-1),M表示通气管道的数目。
第二行有N个整数Vi(0<=Vi<=1000),分别代表每个教室的人数。
接下来有M行,每行两个整数Ai,Bi(0<=Ai,Bi<N),表示教室Ai和教室Bi之间建了一个通气管道。
Output
对于每组数据,请在一行里面输出所求的差值。
如果不管封闭哪条管道都不能把教室分成两个教室群,就输出"impossible"。
题目大意:
给定一个n个点m条边的联通无向图,每个点有一个点权,要求在其中选择一条边删去使得删去后能使原图变成两个连通块,并使两个联通块的点权和之差的绝对值最小,输出最小值,如果不存在合法方案,输出impossible。
输入样例:
4 3 1 1 1 1 0 1 1 2 2 3 4 3 1 2 3 5 0 1 1 2 2 3
输出样例:
0 1
解析:
首先个人觉得这个把这个题目读懂就挺难的。。
根据题意,我们需在图上删掉一条边以后的需要达到的第一个要求是能够把这个图分成两个连通块,这说明这条边一定是原图的一条割边,那么我们的问题转化为了:在图中找到割边,删除并更新答案。
接下来就比较好想了,可以参考POJ 3140,那个题的意思是在一棵树中删除一条边使得产生的两个联通块的权值之和的差的绝对值最小,通过一遍dfs就可以解决(本质上是树形dp)。
我们知道,将一个图中的所有边双联通分量缩点重新构图可以得到一个森林,如果原图联通那么会得到一棵树。所以我们可以初步考虑在图中求出所有边双联通分量然后缩点得到树再跑一遍POJ 3140就好了。
但是仔细考虑之后可以发现缩点不是必要的,我们在求出一个边双的时候可以直接把它的等价权值算出来(tarjan最后回溯的时候用栈维护一下,具体见代码),因为回溯是自底向上的,所以我们如果每次将这个等价权值加到这个点双的位于割边上的节点权值上,就可以得到以当前点双“缩成的点”为根的子树权值,假设是tmp。因为整个图的总权值已知,假设是sum,故我们如果删除当前得到的边双对应的割边,得到的两个连通块的权值和就分别为tmp和sum-tmp,故可以用abs(sum-2*tmp)去更新答案。
讲得挺绕的,如果不能理解不缩点的做法,可以看后面的缩点做法代码。
AC代码(不缩点):
#include<iostream> #include<string.h> #include<math.h> #include<vector> #include<queue> #include<map> #include<algorithm> #include<stack> using namespace std; typedef long long ll; int idx,dfn[10010],low[10010],n,m,val[10010],ans,sum,cnt; vector<int> g[10010]; stack<int> s; void ini(){ ans=1e9; memset(low,0,sizeof dfn); memset(dfn,0,sizeof low); for (int i=0;i<10010;i++) g[i].clear(); idx=sum=cnt=0; while (!s.empty()) s.pop(); } void tarjan(int now,int fa){ low[now]=dfn[now]=++idx; int f=0;//判断重边,从一个点第一次回到父亲不算 s.push(now); for (int nx:g[now]){ if (nx==fa){ f++; if (f==1) continue; } if (!dfn[nx]){ tarjan(nx,now); low[now]=min(low[now],low[nx]); } else low[now]=min(low[now],dfn[nx]); if (low[nx]>dfn[now]){//得到割边,对应一个边双 cnt++; int tmp=0,tp; while (1){ tp=s.top(); tmp+=val[tp]; s.pop(); if (tp==nx) break; } ans=min(ans,abs(sum-2*tmp)); val[now]+=tmp; } } } int main(){ while (cin>>n>>m){ ini(); for (int i=0;i<n;i++) cin>>val[i],sum+=val[i]; while (m--){ int u,v; cin>>u>>v; g[u].push_back(v); g[v].push_back(u); } tarjan(0,-1); if (cnt==0) cout<<"impossible"<<endl; else cout<<ans<<endl; } return 0; }
AC代码(缩点):
#include<iostream> #include<string.h> #include<math.h> #include<vector> #include<queue> #include<map> #include<algorithm> #include<stack> using namespace std; typedef long long ll; int idx,dfn[10010],low[10010],n,m,val[10010],ans,sum,cnt,col[10010],c[10010],book[10010];//col[i]表示i属于哪个bcc 注意这里的cnt表示bcc个数而非割点个数 vector<int> g[10010],a[10010];//g原图 a缩点图 map<int,map<int,int>> mp; stack<int> s; void ini(){ ans=1e9; memset(low,0,sizeof dfn); memset(dfn,0,sizeof low); memset(col,0,sizeof col); memset(c,0,sizeof c); memset(val,0,sizeof val); memset(book,0,sizeof book); for (int i=0;i<10010;i++) g[i].clear(); for (int i=0;i<10010;i++) a[i].clear(); mp.clear(); idx=sum=cnt=0; while (!s.empty()) s.pop(); } void tarjan(int now,int fa){ dfn[now]=low[now]=++idx; int f=0;//防止重边的情况 s.push(now); for (int i=0;i<g[now].size();i++){ int nx=g[now][i]; if (nx==fa){ f++; if (f==1) continue; } if (!dfn[nx]){ tarjan(nx,now); low[now]=min(low[now],low[nx]); } else low[now]=min(low[now],dfn[nx]); if (low[nx]>dfn[now]) mp[now][nx]=mp[nx][now]=1; } } void co(int now,int fa){ col[now]=cnt; for (int i=0;i<g[now].size();i++){ int nx=g[now][i]; if (nx==fa || mp[now][nx] || col[nx]) continue; co(nx,now); } } void dfs(int now,int fa){ int tmp=c[now]; for (int i=0;i<a[now].size();i++){ int nx=a[now][i]; if (book[nx]) continue; book[nx]=1; dfs(nx,now); tmp+=c[nx]; } ans=min(ans,abs(sum-2*tmp)); c[now]=tmp; } int main(){ while (cin>>n>>m){ ini(); for (int i=0;i<n;i++) cin>>val[i],sum+=val[i]; for (int i=1;i<=m;i++){ int u,v; cin>>u>>v; g[u].push_back(v); g[v].push_back(u); } tarjan(0,-1); for (int i=0;i<n;i++){ if (!col[i]){ cnt++; co(i,i); } } for (int i=0;i<n;i++) c[col[i]]+=val[i]; for (int i=0;i<n;i++){ for (int j=0;j<g[i].size();j++){ if (col[i]==col[g[i][j]]) continue; a[col[i]].push_back(col[g[i][j]]); a[col[g[i][j]]].push_back(col[i]); } } book[1]=1; dfs(1,1); if (cnt==1) cout<<"impossible"<<endl; else cout<<ans<<endl; } return 0; }
缩点的版本一开始因为最后判断impossible的条件写成了cnt==0卡了好久。。需要注意这里的cnt表示边双个数而非割边数。

浙公网安备 33010602011771号