题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=2242
题目大意:给定一张图,每个顶点都有一个权值,可能会有重边,要从图中删去某条边使得图分成两部分,求这两部分最小权值和之差,如果没办法分成两部分,则输出impossible.
解题思路:图论和树形DP综合题。如果给定的图是一棵树,那么用树形DP就可以解决,那么此时不存在impssible的情况。但是题目没说明给定的图是树形图,而有可能含有双连通分量(如果删除任意一条边,都不能改变连通分量的连通性),那这时就 要把这个双连通分量压缩成一个点,然后用压缩后的点建一棵树,接着就可以在树上进行DP,删去某条边(u->v),看以u为根的子树和以v为根的子树权值和相差多少。
想清楚上面的过程之后,问题就变成了如何缩点,这个着实让我蛋疼,特地花了一天时间看强连通分量和双连通分量,学会运用优美的Trajan算法,详细的算法大家可以百度Google去。本题用trajan将图中的每座桥都找出来,把桥两边的顶点存进一个数组,然后用这些顶点重构一棵树。桥也叫割边,那是什么?可以简单理解成去掉这条边,连通图就不连通了,我是理解地不深,就简单理解了。
本题有个Trick,那就是出现重边的时候,这条边也要处理,所以必须特判。
测试数据:
2 2
1 1
1 2
2 1
4 5
1 2 3 4
0 1
1 2
2 0
1 0
1 3
1 0
1
4 3
1 2 3 5
0 1
1 2
2 3
4 3
1 1 1 1
0 1
1 2
2 3
代码:
题目大意:给定一张图,每个顶点都有一个权值,可能会有重边,要从图中删去某条边使得图分成两部分,求这两部分最小权值和之差,如果没办法分成两部分,则输出impossible.
解题思路:图论和树形DP综合题。如果给定的图是一棵树,那么用树形DP就可以解决,那么此时不存在impssible的情况。但是题目没说明给定的图是树形图,而有可能含有双连通分量(如果删除任意一条边,都不能改变连通分量的连通性),那这时就 要把这个双连通分量压缩成一个点,然后用压缩后的点建一棵树,接着就可以在树上进行DP,删去某条边(u->v),看以u为根的子树和以v为根的子树权值和相差多少。
想清楚上面的过程之后,问题就变成了如何缩点,这个着实让我蛋疼,特地花了一天时间看强连通分量和双连通分量,学会运用优美的Trajan算法,详细的算法大家可以百度Google去。本题用trajan将图中的每座桥都找出来,把桥两边的顶点存进一个数组,然后用这些顶点重构一棵树。桥也叫割边,那是什么?可以简单理解成去掉这条边,连通图就不连通了,我是理解地不深,就简单理解了。
本题有个Trick,那就是出现重边的时候,这条边也要处理,所以必须特判。
测试数据:
2 2
1 1
1 2
2 1
4 5
1 2 3 4
0 1
1 2
2 0
1 0
1 3
1 0
1
4 3
1 2 3 5
0 1
1 2
2 3
4 3
1 1 1 1
0 1
1 2
2 3
代码:
#include <stdio.h>
#include <math.h>
#include <string.h>
#define MAX 41000
#define min(a,b) (a)<(b)?(a):(b)
struct node {
int v,y;
node *next;
};
int n,m,ptr,cnt,sum,top,st[MAX];
int tot,bl[MAX],vis[MAX],minn,dp[MAX];
int dfn[MAX],low[MAX],val[MAX],index;
node *head[MAX],tree[MAX],bridge[MAX];
void Initial() {
minn = 2147483647;
cnt = sum = tot = 0;
ptr = top = index = 1;
memset(dp,0,sizeof(dp));
memset(dfn,0,sizeof(dfn));
memset(vis,0,sizeof(vis));
memset(head,NULL,sizeof(head));
}
void AddEdge(int x,int y) {
tree[ptr].v = y;
tree[ptr].next = head[x],head[x] = &tree[ptr++];
tree[ptr].v = x;
tree[ptr].next = head[y],head[y] = &tree[ptr++];
}
void Trajan(int i,int fa) {
node *p = head[i];
int v = -1,flag = 1;
st[++top] = i,vis[i] = 1;
dfn[i] = low[i] = index++;
while (p != NULL) {
if (p->v == fa && flag) { //去掉一条指向父亲的边,即允许有重边
flag = 0;
p = p->next;
continue;
}
if (vis[p->v] == 0) { //父子边
Trajan(p->v,i);
low[i] = min(low[i],low[p->v]);
if (low[p->v] > dfn[i]) {
bridge[++tot].v = i;
bridge[tot].y = p->v;
}
}
else if (vis[p->v] == 1) //返祖边
low[i] = min(low[i],dfn[p->v]);
p = p->next;
}
vis[i] = 2; //表示已缩点
if (low[i] == dfn[i]) {
cnt++;
while (v != i) {
v = st[top--];
bl[v] = cnt;
dp[cnt] += val[v];
}
}
}
void CreateNew() {
ptr = 1;
memset(head,NULL,sizeof(head));
for (int i = 1; i <= tot; ++i) {
int x = bl[bridge[i].v];
int y = bl[bridge[i].y];
AddEdge(x,y);
}
}
void Tree_DP(int v,int pa) {
node *p = head[v];
while (p != NULL) {
if (p->v != pa) {
Tree_DP(p->v,v);
dp[v] += dp[p->v];
}
p = p->next;
}
minn = min(minn,abs(sum - dp[v] * 2));
}
int main()
{
int i,j,k,a,b;
while (scanf("%d%d",&n,&m) != EOF) {
Initial();
for (i = 1; i <= n; ++i)
scanf("%d",&val[i]),sum += val[i];
for (i = 1; i <= m; ++i)
scanf("%d%d",&a,&b),AddEdge(a+1,b+1);
Trajan(1,0);
if (tot == 0) {
printf("impossible\n");
continue;
}
CreateNew();
Tree_DP(1,0);
printf("%d\n",minn);
}
}
本文ZeroClock原创,但可以转载,因为我们是兄弟。