Hdu 2242 考研路茫茫——空调教室 (DP_树形DP(Tarjan))

题目链接: 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


代码:
#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原创,但可以转载,因为我们是兄弟。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值