缩点
题目描述
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入输出格式
输入格式:
第一行,n,m
第二行,n个整数,依次代表点权
第三至m+2行,每行两个整数u,v,表示u->v有一条有向边
输出格式:
共一行,最大的点权之和。
缩点,就是把一张有向有环图中的环缩成一个个点,形成一个有向无环图。根据题目意思,我们只需要找出一条点权最大的路径就行了,不限制点的个数。那么考虑对于一个环上的点被选择了,一整条环是不是应该都被选择,这一定很优,能选干嘛不选。很关键的是题目还允许我们重复经过某条边或者某个点,我们就不需要考虑其他了。因此整个环实际上可以看成一个点(选了其中一个点就应该选其他的点)
#include <iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define ll long long
const int maxn=2e4+10;
const int maxm=2e5+7;
struct Edge{
int to,next;
}e[maxm<<1];
int head[maxn],tol;
int low[maxn],dfn[maxn],stk[maxn],color[maxn];
int ind,top;
ll w[maxn];
ll val[maxn],vis[maxn];
int scc;
bool in_stk[maxn];
inline void add(int u,int v){
tol++;e[tol].to=v;e[tol].next=head[u];head[u]=tol;
}
struct node{
int x,y;
}s[maxm];
void tarjan(int u){
int v;
low[u]=dfn[u]=++ind;
stk[top++]=u;
in_stk[u]= true;
for(int i=head[u];i;i=e[i].next){
v=e[i].to;
if(!dfn[v]){
tarjan(v);
if(low[u]>low[v]){
low[u]=low[v];
}
}else if(in_stk[v]&&low[u]>dfn[v]){
low[u]=dfn[v];
}
}
if(low[u]==dfn[u]){
scc++;
do{
v=stk[--top];
in_stk[v]=false;
color[v]=scc;
w[scc]+=val[v];
}while (v!=u);
}
}
ll dfs(int u){
if(!vis[u]) {
vis[u]=w[u];
for (int i = head[u]; i; i = e[i].next) {
vis[u]=max(vis[u],dfs(e[i].to)+w[u]);
}
}
return vis[u];
}
int main(){
int n,m;scanf("%d%d",&n,&m);
for (int i =1; i <=n ; ++i) {
scanf("%lld",&val[i]);
}
for (int i =1; i <=m ; ++i) {
scanf("%d%d",&s[i].x,&s[i].y);
add(s[i].x,s[i].y);
}
for(int i=1;i<=n;++i){
if(!dfn[i]){
tarjan(i);
}
}
tol=0;
memset(head,0,sizeof(head));
for(int i=1;i<=m;++i){
if(color[s[i].x]!=color[s[i].y]){
add(color[s[i].x],color[s[i].y]);
}
}
ll maxx=0;
for(int i=1;i<=scc;++i){
maxx=max(maxx,dfs(i));
}
printf("%lld\n",maxx);
}
不要忘记努力,不要辜负自己
欢迎指正 QQ:1468580561