【Codeforces Round #586 (Div. 1 + Div. 2) E. Tourism】 Tarjan缩点+树形DP

题目链接

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 1n,m2105

做法

分析一下题意发现,如果无向图中出现环,那么环中每个点都可以遍历一遍,并可以从任意一个点走出环。
于是一个环中的点就变成了命运共同体,所以我们首先选择缩点,缩点之后变成了一棵树,我们设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
#include
#include
#include
#include
#include
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<<" = "<
#define pb push_back
#define dbg(x) cout<<#x<<" = "<
#define dbg2(x1,x2) cout<<#x1<<" = "<#define dbg3(x1,x2,x3) cout<<#x1<<" = "<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;
}

你可能感兴趣的:(Codeforces,tarjan,树形DP)