题目链接
http://codeforces.com/contest/1220/problem/E
题意
给你一个n个点m条边的无向联通图,每个点有一个点权,现在给出起点s,找出一条点权和最大的路径,满足不能连续走同一条边两次,而且多次经过同一个点时,只获得一次点权。
1 ≤ n , m ≤ 2 ∗ 1 0 5 1 \leq n,m \leq 2*10^5 1≤n,m≤2∗105
做法
分析一下题意发现,如果无向图中出现环,那么环中每个点都可以遍历一遍,并可以从任意一个点走出环。
于是一个环中的点就变成了命运共同体,所以我们首先选择缩点,缩点之后变成了一棵树,我们设S为根,进行树上DP即可。
DP方程的定义为
sum[rt] 表示缩点之后每个联通分量的权值和
dp[rt][0] 表示以rt为根的子树内从rt出发回到rt的最大权值路径和
dp[rt][1] 表示以rt为根的子树内从rt出发的最大权值路径和
首先对于每个叶子节点来说,如果叶子节点所在的联通分量点数超过2,那么
d
p
[
r
t
]
[
0
]
=
d
p
[
r
t
]
[
1
]
=
s
u
m
[
r
t
]
dp[rt][0]=dp[rt][1]=sum[rt]
dp[rt][0]=dp[rt][1]=sum[rt]
否则
d
p
[
r
t
]
[
1
]
=
s
u
m
[
r
t
]
dp[rt][1] = sum[rt]
dp[rt][1]=sum[rt]
对于每个非叶子节点,首先我们把所有儿子中可以回到根节点的dp值加起来,这样就保证我们一定可以回到根节点,之后我们要看整个子树内是否有一个环可以使我们回到根节点,有的话
d
p
[
r
t
]
[
0
]
dp[rt][0]
dp[rt][0]就等于所有
d
p
[
t
o
]
[
0
]
dp[to][0]
dp[to][0]的和加上
s
u
m
[
r
t
]
sum[rt]
sum[rt],否则
d
p
[
r
t
]
[
0
]
=
0
dp[rt][0]=0
dp[rt][0]=0。之后我们看
d
p
[
r
t
]
[
1
]
dp[rt][1]
dp[rt][1]如何转移,首先我们还是得到所有
d
p
[
t
o
]
[
r
t
]
dp[to][rt]
dp[to][rt]的和,我们要在所有
t
o
to
to中,选择一个
d
p
[
t
o
]
[
1
]
−
d
p
[
t
o
]
[
0
]
dp[to][1]-dp[to][0]
dp[to][1]−dp[to][0]最大的,让他作为那个不需要返回的路径即可,而且不论子树内是否存在环,
d
p
[
r
t
]
[
1
]
dp[rt][1]
dp[rt][1]都要加上
s
u
m
[
r
t
]
sum[rt]
sum[rt]。
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<math.h>
#include<map>
#include<vector>
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int maxn = 4e5+10;
#define Fi first
#define Se second
#define dbg(x) cout<<#x<<" = "<<x<<endl
#define pb push_back
#define dbg(x) cout<<#x<<" = "<<x<<endl
#define dbg2(x1,x2) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<endl
#define dbg3(x1,x2,x3) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<" "<<#x3<<" = "<<x3<<endl
int low[maxn],dfn[maxn],root[maxn],cnt,sum,tot,head[maxn],fa[maxn],num[maxn],flag[maxn],val[maxn];
map<pii,int> mp;
vector<int> G[maxn];
ll arr[maxn],dp[maxn][2],can[maxn];
struct node
{
int to,nxt;
};
node edge[maxn*4];
int Find(int x)
{
return x==root[x]?x:root[x]=Find(root[x]);
}
void uno(int x,int y)
{
int fx=Find(x),fy=Find(y);
if(fx!=fy) root[fy]=fx;
}
void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].nxt=head[u];
head[u]=tot++;
}
void tarjan(int u,int fat)
{
dfn[u]=low[u]=++cnt;
fa[u]=fat;
int ff=0;
for(int i=head[u];i+1;i=edge[i].nxt)
{
int v=edge[i].to;
if(!dfn[v])
{
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]<=dfn[u]) uno(u,v);
}
else if(dfn[v])
{
if(v==fat)
{
if(ff==0) ff++;
else ff=0,low[u]=min(low[u],dfn[v]);
}
else if(v!=fat) low[u]=min(low[u],dfn[v]);
}
}
}
void dfs(int rt,int fa)
{
if(num[rt]>=2) can[rt]=1;
ll maxx=0;
for(int i=0;i<G[rt].size();i++)
{
int to=G[rt][i];
if(to==fa) continue;
dfs(to,rt);
dp[rt][0]+=dp[to][0];
maxx=max(maxx,dp[to][1]-dp[to][0]);
can[rt]|=can[to];
}
dp[rt][1]=dp[rt][0]+maxx+arr[rt];
if(can[rt]) dp[rt][0]+=arr[rt];
else dp[rt][0]=0;
}
int main()
{
//freopen("E.in","r",stdin);
int n,m,s;
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++) { head[i]=-1; root[i]=i; }
for(int i=1;i<=n;i++) scanf("%d",&val[i]);
for(int i=0;i<m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
addedge(a,b);
addedge(b,a);
}
scanf("%d",&s);
tarjan(1,1);
for(int i=1;i<=n;i++) fa[i]=Find(i);
int cnt=0;
for(int i=1;i<=n;i++) if(fa[i]==i) flag[i]=++cnt;
for(int i=1;i<=n;i++)
{
for(int j=head[i];j+1;j=edge[j].nxt)
{
int u=i;
int v=edge[j].to;
if(fa[u]==fa[v]) continue;
int x=flag[fa[u]];
int y=flag[fa[v]];
if(mp.find(pii(x,y))!=mp.end()) continue;
mp[pii(x,y)]=1;
G[x].push_back(y);
}
}
for(int i=1;i<=n;i++) num[flag[fa[i]]]++,arr[flag[fa[i]]]+=val[i];
dfs(flag[fa[s]],-1);
printf("%lld\n",dp[flag[fa[s]]][1]);
return 0;
}