题目描述:
第一行为一个正整数 N。
第二行有N个非负整数A[i],表示了每个分部的orzFang价值。
第三行有N个正整数F[i],表示通过第i个分部的虫洞所到达的分部为 F[i],可能出现 F[i]=i的情况。
求:
从第 i 个分部出发,orzFang 价值之和的最大值为多少。
10
1 1 1 1 1 1 1 1 1 1
7 4 5 3 9 1 10 3 4 5
7 5 4 4 4 8 6 5 4 5
思路:tarjan缩点。对于缩点后我们发现只会是一条链子。但是直接每个点dfs是O(n^2)超时的。于是这里有一个类似前缀和的dp优化。dp[u]+=dp[v],通俗来说就是树上后序遍历。
复习了一下好久没出现的tarjan
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<stack>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=2e5+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
vector<LL>g[maxn];
stack<LL>s;
bool instack[maxn];
LL dfn[maxn],low[maxn],times=0,cnt=0,col[maxn];
LL money[maxn];
LL A[maxn],F[maxn];
LL dp[maxn];
bool vis[maxn];
void tarjan(LL x){
dfn[x]=low[x]=++times;
s.push(x);instack[x]=true;
for(LL i=0;i<g[x].size();i++){
LL to=g[x][i];
if(!dfn[to]){
tarjan(to);
low[x]=min(low[x],low[to]);
}
else if(instack[to]) low[x]=min(low[x],dfn[to]);
}
if(dfn[x]==low[x]){
cnt++;
LL y;
do{
y=s.top();
col[y]=cnt;///缩点
money[cnt]+=A[y];
instack[y]=false;
s.pop();
}while(y!=x);
return;
}
}
LL dfs(LL u){
if(vis[col[u]]){
return dp[col[u]];
}
vis[col[u]]=true;
dp[col[u]]=money[col[u]];
for(LL i=0;i<g[u].size();i++){
LL v=g[u][i];
if(col[v]==col[u]) continue;
dp[col[u]]+=dfs(v);
}
return dp[col[u]];
}
int main(void)
{
///cin.tie(0);std::ios::sync_with_stdio(false);
LL n;n=read();
for(LL i=1;i<=n;i++) A[i]=read();
for(LL i=1;i<=n;i++){
F[i]=read();
if(F[i]==i) continue;
g[i].push_back(F[i]);
}
for(LL i=1;i<=n;i++){
if(!dfn[i]) tarjan(i);
}
/*
for(LL i=1;i<=cnt;i++){
cout<<money[i]<<" ";
}
cout<<endl;*/
for(LL i=1;i<=n;i++){
if(!vis[col[i]]){///如果该缩点编号没有访问过
LL ans1=dfs(i);
/// debug(ans1);
printf("%lld\n",dp[col[i]]);
}
else if(vis[col[i]]){
LL ans2=dp[col[i]];
///debug(ans2);
printf("%lld\n",dp[col[i]]);///输出这个缩点编号的最长链和
}
}
return 0;
}