POJ3160
题意:
有向图N<=30000,M<=150000,每个点有点权
每个点跟路径都能经过多次,到达每个点后可选择进屋,若进屋就获得该点的点权
问能获得的最大点权
思路:
直接跑记忆化,有环,会T,加个vis也不行,因为每个点能经过多次
考虑贪心,在一个强连通分量里,能加分的就尽量加分
说到强连通我们就会想到tarjan
那么我们缩点得到DAG之后跑记忆化就行了
DPi表示从i点集出发能获得的最大值
79ms
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
const int maxn=30005;
const int maxe=150005;
int low[maxn];
int dfn[maxn];
bool ins[maxn];
int sk[maxn];
int poi=0,idx=0,cnt=0;
int num=0;
int head[maxn];
int to[maxe*2];
int nxt[maxe*2];
int dp[maxn];
int val[maxn];
vector<int>dag[maxn];
int scc[maxn];
int scv[maxn];
void add(int u,int v)
{
cnt++;nxt[cnt]=head[u];
head[u]=cnt; to[cnt]=v;
}
void tarjan(int u)
{
dfn[u]=low[u]=++idx;
ins[u]=1;
sk[poi++]=u;
for(int i=head[u];i!=-1;i=nxt[i])
{
int v=to[i];
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(ins[v]) low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
num++;
int t;
do{
t=sk[--poi];
scc[t]=num;
ins[t]=0;
scv[num]+=max(0,val[t]);
}
while(t!=u);
}
}
int dfs(int u)
{
if(dp[u]!=0)return dp[u];
for(int i=0;i<dag[u].size();i++)
{
int v=dag[u][i];
dp[u]=max(dp[u],dfs(v));
}
dp[u]+=scv[u];
return dp[u];
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
int u,v;
memset(ins,0,sizeof(ins));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(scv,0,sizeof(scv));
memset(dp,0,sizeof(dp));
memset(head,-1,sizeof(head));
num=poi=cnt=idx=0;
for(int i=1;i<=n;i++)
scanf("%d",&val[i]);
for(int i=1;i<=m;i++)
{
scanf("%d %d",&u,&v);
u++,v++;
add(u,v);
}
for(int i=1;i<=n;i++)
{
dag[i].clear();
if(!dfn[i])
tarjan(i);
}
for(int u=1;u<=n;u++)
{
for(int i=head[u];i!=-1;i=nxt[i])
{
int v=to[i];
if(scc[u]!=scc[v])
{
dag[scc[u]].push_back(scc[v]);
}
}
}
int ans=0;
for(int i=1;i<=num;i++)
{
if(dp[i]==0)
ans=max(ans,dfs(i));
}
printf("%d\n",ans);
}
}