CodeForces - 1220E Tourism 无向图缩点+树形dp

题目链接:https://vjudge.net/problem/CodeForces-1220E

题意:n个点m条边的无向连通图,起点s,每个点有权值,求遍历无向图得到的最大权值和。但是不能走回头路,即如果从U走到V那么下一步不可以从V走到U

题解:先缩点再跑个树形dp就可以了,无向图缩点还是第一次写,栈内记录的是边,是通过割边来处理的环,转化成一个树之后,如果他的孩子节点是由环缩成的,那这个分支也能都走到,我们就把这个分支的val直接加到父亲节点,该节点变为0。

双连通分量:https://blog.csdn.net/huzecong/article/details/17553335

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
vector<int> v[N], vc[N];
int dfn[N], low[N];
int vis[N], id[N]; 
ll val[N], w[N];
int tot, cnt;
int sz[N];
int n, m;
struct edge {
	int x, y;
	edge(){}
	edge(int x_, int y_) {
		x = x_;
		y = y_;
	}
}sta[N], tmp;
int len;
void tarjan(int u, int fa) {
	low[u] = dfn[u] = ++tot;
	int to;
	int x, y;
	int flag;
	for(int i = 0; i < v[u].size(); i++) {
		to = v[u][i];
		if(!dfn[to]) {
			
			sta[++len] = edge(u, to);
			tarjan(to, u);
			low[u] = min(low[u], low[to]);
		//	cout << u << " " << dfn[u] << " " << to << " " << low[to] << endl;
			if(low[to] >= dfn[u]) {
				cnt++;
				flag = 0;
				while(len) {
					tmp = sta[len--];
					if(tmp.x == u && tmp.y == to) break;	
					flag = 1;
					if(id[tmp.x]) val[id[tmp.x]] -= w[tmp.x];
					if(id[tmp.y]) val[id[tmp.y]] -= w[tmp.y];
					
					val[cnt] += w[tmp.y] + w[tmp.x];		
					id[tmp.y] = cnt;
					id[tmp.x] = cnt;
				}
				if(!flag && !id[to]) {
					id[to] = cnt;
					val[cnt] = w[to];
				}
			} 
		} else if(to != fa) {
			low[u] = min(low[u], dfn[to]);
			if(dfn[u] > dfn[to])
				sta[++len] = edge(u, to);
		}
	}
}
int st;
ll dp[N];
int f[N];
void dfs1(int u, int fa) {
	int to;
	for(int i = 0; i < vc[u].size(); i++) {
		to = vc[u][i];
		if(to == fa) continue;
		dfs1(to, u);
		if(sz[to] > 1) {
			val[u] += val[to];
			sz[u] += sz[to];
			val[to] = 0;
		}
	}
}
void dfs(int u, int fa) {
	int to;
	for(int i = 0; i < vc[u].size(); i++) {
		to = vc[u][i];
		if(to == fa) continue;
		dfs(to, u);
		dp[u] = max(dp[u], dp[to]);
	}
	dp[u] += val[u];
}
int fath(int x) {
	return x == f[x] ? f[x] : f[x] = fath(f[x]);
}
int main() {
	int x, y;
	int xx, yy;
	scanf("%d %d", &n, &m);
	for(int i = 1; i <= n; i++) f[i] = i;
	for(int i = 1; i <= n; i++) scanf("%lld", &w[i]);
	for(int i = 1; i <= m; i++) {
		scanf("%d %d", &x, &y);
		v[x].push_back(y);
		v[y].push_back(x);
	}
	tarjan(1, 0);
	if(!id[1]) {
		id[1] = ++cnt;
		val[cnt] = w[1];
	}
	for(int i = 1; i <= n; i++) {
		sz[id[i]]++;
	//	cout << i << " -- " << id[i] << endl;
		for(int j = 0; j < v[i].size(); j++) {
			x = id[i];
			y = id[v[i][j]];
			xx = fath(x);
			yy = fath(y);
			if(xx != yy) {
				f[xx] = yy;
				vc[x].push_back(y);
				vc[y].push_back(x);
			}
		}
	}
	scanf("%d", &st);
	if(n == 1) {
		printf("%lld\n",  w[1]);
		return 0;
	}
	dfs1(id[st], -1);
	dfs(id[st], -1);
	printf("%lld\n", dp[id[st]]);
	return 0;
}
/*
6 7
1 2
1 3
2 3
3 4
4 5
4 6
5 6


5 6
1 2
2 3
3 1
3 4
4 5
5 3
*/

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值