cf 1220E.Tourism(Tarjan缩点+树形dp)

31 篇文章 0 订阅
13 篇文章 2 订阅

传送门

题意

在一个无向图上有 n n n 个城市,每个城市都有一个价值 w i w_i wi,现从起点 s s s 出发,过程中不能连续两次经过同一条边,问能够得到的最大价值。

由于不能连续两次经过同一条边,因此一定会贪心的选择所有带有环的路,最后再选择一条贡献最大但没有环的路走到底。

采用 T a r j a n Tarjan Tarjan 缩点转化为有向无环图之后,与起点 s s s 在同一连通块的可以看作是以 s s s 为根的有向树(返祖边可以在 d f s dfs dfs 中特判掉)。在 T a r j a n Tarjan Tarjan 中处理每个强连通分块是单点还是环,并以 y e s yes yes 数组标记,则在树中若某一结点的后代存在 y e s = 1 yes=1 yes=1,则可以经过该结点到最底端的 y e s = 1 yes=1 yes=1 的结点并通过绕环再回来,显然这整段的贡献值都能够被纳入答案。

由于最后会选择贡献最大但没有环的路走到底(如果存在的话),因此对于某个儿子是 y e s = 0 yes=0 yes=0 的结点,都需要更新无环路能够取到的贡献最大值。但对于当前结点而言,只有其所有儿子都是 y e s = 0 yes=0 yes=0,才能够更新这条无环路的贡献,否则当前结点的贡献会被重复计算(如果有儿子的 y e s = 1 yes=1 yes=1,该结点一定会被纳入答案内,若更新了无环路,可能会多计算一次)。

#include <bits/stdc++.h>
#define int long long
#define PII pair<int,int>
using namespace std;

const int N=2e5+10;
vector <int> h[N],H[N];
bool vis[N],in[N],yes[N];
int dfn[N],low[N],suo[N],num[N],value[N],total[N],fail[N],dp[N];
stack<int> st;
vector<PII> q;
int tmp=0,cnt=0,x=0,sum=0,maxx=0;
int ans=0;

void tarjan(int u,int fa){
	dfn[u]=low[u]=++x;
	in[u]=true;
	st.push(u);
	
	for(auto v:h[u]){
		if(v==fa)
			continue;
		if(!dfn[v]){
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
		}
		else if(in[v])
			low[u]=min(low[u],dfn[v]);
	}
	
	if(dfn[u]==low[u]){
		cnt++;
		do{
			tmp=st.top();
			//cout<<tmp<<" "<<value[tmp]<<endl;
			st.pop();
			in[tmp]=false;
			suo[tmp]=cnt;
			total[cnt]+=value[tmp];
			if(u!=tmp)
				yes[cnt]=1;//该强连通分量内有环 	
		}
		while(u!=tmp);
	}
	dp[cnt]=total[cnt];
}

void dfs(int u){
	vis[u]=1;
	for(auto i:H[u]){
		if(vis[i])
			continue;
		dfs(i);
		if(yes[i]){
			yes[u]=1;
			dp[u]+=dp[i];
		}
		else{
			fail[u]=max(fail[u],fail[i]);
		}
	}
	if(!yes[u]){
		fail[u]+=total[u];
	}
	maxx=max(maxx,fail[u]);
} 


signed main(){
	int n,m,s,u,v;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>value[i];
	
	for(int i=1;i<=m;i++){
		cin>>u>>v;
		q.push_back({u,v});
		q.push_back({v,u});
		h[u].push_back(v);
		h[v].push_back(u);
	}
	cin>>s;
	for(int i=1;i<=n;i++){
		if(!dfn[i])
			tarjan(i,0);
	}
	
//	for(int i=1;i<=n;i++)
//		cout<<suo[i]<<" ";
//	cout<<endl;
//	for(int i=1;i<=cnt;i++)
//		cout<<total[i]<<" ";
//	cout<<endl;
	
	for(auto i:q){//建新图 
		u=suo[i.first],v=suo[i.second];
		if(u==v)
			continue;
		H[u].push_back(v);
	}
	
	dfs(suo[s]);//预处理每条路径是否可返回
	if(yes[suo[s]])
		cout<<dp[suo[s]]+maxx;
	else
		cout<<max(dp[suo[s]],maxx);
	
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值