题目背景
缩点+DP
题目描述
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入输出格式
输入格式:
第一行,n,m
第二行,n个整数,依次代表点权
第三至m+2行,每行两个整数u,v,表示u->v有一条有向边
输出格式:
共一行,最大的点权之和。
输入输出样例
说明
n<=10^4,m<=10^5,0<=点权<=1000
算法:Tarjan缩点+DAGdp
#include<bits/stdc++.h>
using namespace std;
struct edge{
int nw,nxt,mark;
}pre[100010];
int n,m,idx,cnt;
int dfn[10010],low[10010];
int in[10010],v[10010],fa[10010];
int head[10010];
bool used[10010];
int stk[10010],p;
int ans=0;
void add (int x,int y,int cnt)
{
pre[cnt].nw=x;
pre[cnt].mark=head[x];
pre[cnt].nxt=y;
head[x]=cnt;
}
void tarjan (int u)
{
dfn[u]=low[u]=++idx;
stk[++p]=u;
used[u]=1;
for (int i=head[u];i!=0;i=pre[i].mark)
{
int nx=pre[i].nxt;
if (!dfn[nx])
{
tarjan (nx);
low[u]=min (low[u],low[nx]);
}
else if (used[nx])
low[u]=min (low[u],dfn[nx]);
}
if (low[u]==dfn[u])
{
do{
v[u]+=v[stk[p]];
fa[stk[p]]=u;
used[stk[p]]=0;
p--;
}while (stk[p+1]!=u);
v[u]>>=1;
}
}
int topo ()
{
int dis[10010];
queue<int>q;
for (int i=1;i<=n;i++)
if (fa[i]==i)
{
dis[i]=v[i];
if (!in[i])
q.push(i);
}
while (!q.empty())
{
int Now=q.front();
for (int i=head[Now];i!=0;i=pre[i].mark)
{
int Nxt=pre[i].nxt;
dis[Nxt]=max (dis[Nxt],dis[Now]+v[Nxt]);
in[Nxt]--;
if (!in[Nxt])
q.push (Nxt);
}
q.pop();
}
int maxx=0;
for (int i=1;i<=n;i++)
if (fa[i]==i)
maxx=max (maxx,dis[i]);
return maxx;
}
int main()
{
memset (in,0,sizeof (in));
memset (used,0,sizeof (used));
memset (dfn,0,sizeof(dfn));
memset (head,0,sizeof (head));
scanf ("%d%d",&n,&m);
for (int i=1;i<=n;i++)
scanf ("%d",&v[i]);
for (int i=1;i<=m;i++)
{
int a,b;
scanf ("%d%d",&a,&b);
add (a,b,i);
}
for (int i=1;i<=n;i++)
if (!dfn[i])
tarjan (i);
memset (head,0,sizeof (head));
for (int i=1;i<=m;i++)
{
int Now=fa[pre[i].nw];
int Nxt=fa[pre[i].nxt];
if (Now!=Nxt)
{
add (Now,Nxt,++cnt);
in[Nxt]++;
}
}
printf ("%d",topo ());
return 0;
}