[bzoj] 1040 骑士 || 基环外向树dp

原题

给出n个点n条边和每个点的点权,一条边的两个断点不能同时选择,问最大可以选多少。


//图是一张基环外向树森林
是不是很像舞会啊~
就是多了一条边。
所以我们考虑一下对于一棵基环外向树,拆掉一条在环上的边,变成一棵树。在这个树上以断边的一个断点为根,跑舞会,就得到了这棵树的最大值(根选和根不选了两种)。考虑到对于拆下来的内一条边,也要满足断点不能同时选择,所以此时得到的答案中,根不选一定是正确的,但是根选不一定是正确的(因为我们不知道此刻另一个断点是否被选择)。
那么我们强制该点不选,然后更新至根选的状态即可。

#include<cstdio>
#include<algorithm>
#define N 1000010
typedef long long ll;
using namespace std;
int n,a[N],f[N],head[N],ecnt=1,cnt[N];
ll dp[N][2],ans;
bool vis[N];
struct hhh
{
    int to,next;
}edge[N];

int read()
{
    int ans=0,fu=1;
    char j=getchar();
    for (;j<'0' || j>'9';j=getchar()) if (j=='-') fu=-1;
    for (;j>='0' && j<='9';j=getchar()) ans*=10,ans+=j-'0';
    return ans*fu;
}

void add(int u,int v)
{
    edge[ecnt].to=v;
    edge[ecnt].next=head[u];
    head[u]=ecnt++;
}

void dfs(int x)//经典dp
{
    vis[x]=1;
    dp[x][1]=a[x];
    for (int i=head[x];i;i=edge[i].next)
	if (!vis[edge[i].to])
	{
	    dfs(edge[i].to);
	    dp[x][0]+=max(dp[edge[i].to][0],dp[edge[i].to][1]);
	    dp[x][1]+=dp[edge[i].to][0];
	}
}

void DP(int x)
{
    int root;
    for (root=x;cnt[root]!=x;root=f[root])
	cnt[root]=x;
    dfs(root);
    x=f[root];//x为当前根,那么不在树上的那条边就是x和f[x]的边,所以强制f[x]不选
    dp[x][1]=dp[x][0];
    for (x=f[x];x!=root;x=f[x])
    {
	dp[x][0]=0;dp[x][1]=a[x];
	for (int i=head[x];i;i=edge[i].next)
	{
	    dp[x][0]+=max(dp[edge[i].to][0],dp[edge[i].to][1]);
	    dp[x][1]+=dp[edge[i].to][0];
	}
    }
    dp[root][1]=a[root];
    for (int i=head[root];i;i=edge[i].next)
	dp[root][1]+=dp[edge[i].to][0];//强制根选,则+=儿子们都不选
    ans+=max(dp[root][0],dp[root][1]);//取当前基环外向树的最大值
}

int main()
{
    n=read();
    for (int i=1;i<=n;i++)
	a[i]=read(),f[i]=read(),add(f[i],i);
    for (int i=1;i<=n;i++)//基环外向树森林
	if (!vis[i]) DP(i);
    printf("%lld\n",ans);
    return 0;
}
posted @ 2018-01-09 08:53  Mrha  阅读(139)  评论(0编辑  收藏  举报