P3387 【模板】缩点 [强连通分量][DAG]

题意

给定一个 \(n\) 个点 \(m\) 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。

允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次

Solution

先缩点,就成了一个DAG图,做一遍拓扑排序,按拓扑序进行DP。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 10010,M = 100010 * 2;
int head[N],ver[M],nxt[M],hc[N],vc[M],nc[M],tot,tc;
int ins[N],low[N],s[N],dfn[N],c[N],cnt,num,top,sum[N];
vector<int> scc[N];

void add(int x,int y){
	nxt[++tot] = head[x];
	ver[tot] = y;
	head[x] = tot;
}
void addc(int x,int y){
	nc[++tc] = hc[x];
	vc[tc] = y;
	hc[x] = tc;
}
void tarjan(int x){
	low[x] = dfn[x] = ++num;
	s[++top] = x; ins[x] = 1;
	for(int i = head[x]; i; i = nxt[i]){
		int v = ver[i];
		if(!dfn[v]){
			tarjan(v);
			low[x] = min(low[x],low[v]);
		}
		else if(ins[v])
			low[x] = min(low[x],dfn[v]);
	}
	if(low[x] == dfn[x]){
		cnt++; int y;
		do{
			y = s[top--]; ins[y] = 0;
			scc[cnt].push_back(y);
			c[y] = cnt;
		}while(x != y);
	}
}
int n,m,a[N],deg[N],dp[N];
void dfs(int x){
	dp[x] = sum[x];
	for(int i = hc[x]; i; i = nc[i]){
		int v = vc[i];
		dfs(v);
		dp[x] = max(dp[x],dp[v] + sum[x]);
	}
} 
int topo(){
	queue<int> q;
	for(int i = 1; i <= cnt; i++)
		if(!deg[i]) {
			q.push(i);
			dp[i] = sum[i];
		}
	while(!q.empty()){
		int x = q.front(); q.pop();
		for(int i = hc[x]; i; i = nc[i]){
			int y = vc[i];
			dp[y] = max(dp[y],dp[x] + sum[y]);
			deg[y]--;
			if(!deg[y]) 
				q.push(y);
		}
	}
	int ans = 0;
	for(int i = 1; i <= cnt; i++)
		ans = max(ans,dp[i]);
	return ans;
}

int main(){
	cin >> n >> m;
	for(int i = 1; i <= n; i++)
		cin >> a[i];
	for(int i = 1; i <= m; i++){
		int u,v;
		cin >> u >> v;
		add(u,v);
	}
	for(int i = 1; i <= n; i++)
		if(!dfn[i]) tarjan(i);
	for(int x = 1; x <= n; x++){
		for(int i = head[x]; i; i = nxt[i]){
			int v = ver[i];
			if(c[x] == c[v]) continue;
			addc(c[x],c[v]);
			deg[c[v]]++;
		}
	}
	for(int i = 1; i <= cnt; i++){
		for(int j = 0; j < scc[i].size(); j++)
			sum[i] += a[scc[i][j]];
	}
	cout << topo() << endl;
	return 0;
}
posted @ 2020-01-20 21:02  foxc  阅读(136)  评论(0编辑  收藏  举报