题目背景
缩点+DP
题目描述
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入输出格式
输入格式:
第一行,n,m
第二行,n个整数,依次代表点权
第三至m+2行,每行两个整数u,v,表示u->v有一条有向边
输出格式:
共一行,最大的点权之和。
输入输出样例
输入样例#1: 复制
2 2 1 1 1 2 2 1
输出样例#1: 复制
2
说明
n<=10^4,m<=10^5,点权<=1000
算法:Tarjan缩点+DAGdp
1.思路没什么好说题目都告诉你了huaji
2.tarjan的几个总计(写法优化??)
- 可以用!dfn[]来代替!flag[]
- 一定要判断是否已经出栈不然下面这种清空就爆蛤(可以每次出栈时让dfn==0就不用再用flag了)
- 只有dfn==low时弹栈,下面这种写法比代码里的更简洁
while(y=stack[len--])
{
各种骚操作一发.....
if(y==x) break;
}
3.最后的dp也是挺简单的用记忆化搜索d[i]表示以i开头的最长路径
补充:其实还可以拓扑排序再dp,比如从出度为零的点开始向上的顺序也可以
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,num,mid,len,dick;
int a[10005],a1[10005],stack[10005],dfn[10005],head1[10005];
int low[10005],lab[10005],flag[10005];//dfn==dist,low==head2
struct data
{
int head,to;
}edge[100050],edge1[100050];
void init()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int j=1;j<=m;j++)
{
scanf("%d%d",&mid,&edge[j].to);
edge[j].head=head1[mid];
head1[mid]=j;
}
}
void deal()
{
lab[stack[len]]=dick;
flag[stack[len]]=2;
a1[dick]+=a[stack[len]];
len--;
}
int tarjan(int l)
{
dfn[l]=low[l]=++num;
stack[++len]=l;flag[l]=1;
for(int i=head1[l];i;i=edge[i].head)
{
if(!dfn[edge[i].to]) tarjan(edge[i].to);
if(flag[edge[i].to]==1)
low[l]=min(low[edge[i].to],low[l]);
}
if(low[l]==dfn[l])
{
dick++;
while(stack[len]!=l) deal();
deal();
}
}
void build()
{
memset(dfn,0,sizeof(dfn));memset(low,0,sizeof(low));
for(int i=1;i<=n;i++) dfn[lab[i]]+=a[i];
for(int i=1;i<=n;i++)
{
for(int j=head1[i];j;j=edge[j].head)
{
if(lab[i]==lab[edge[j].to]) continue;
edge1[++len].to=lab[edge[j].to];
edge1[len].head=low[lab[i]]; low[lab[i]]=len;
}
}
}
int dp(int k)
{
if(flag[k]) return dfn[k];
flag[k]=1;
for(int i=low[k];i;i=edge1[i].head)
dfn[k]=max(a1[k]+dp(edge1[i].to),dfn[k]);
}
int main()
{
init();
for(int i=1;i<=n;i++) if(!flag[i]) tarjan(i);
build();
num=0; memset(flag,0,sizeof(flag));
for(int i=1;i<=dick;i++) if(!flag[i]) dp(i);
for(int i=1;i<=dick;i++) num=max(num,dfn[i]);
printf("%d",num);
}