[ZJOI2008]骑士

P2607 [ZJOI2008]骑士

题意:

上司的舞会增强版

思路:

因为一个骑士有且只有一个最讨厌的人,而且这个骑士不会讨厌自己,即该图中是没有自环的。

利用有向图去维护信息:

我们把 \(x\) 所讨厌的人 \(y\) 设为 \(x\) 的父亲节点,这样每一个人都有且只有一条出边,每个点的出度只能为 \(1\).

考虑非根节点,又得出结论:每个连通块内有且只有一个简单环。

这样 我们考虑把每个联通块的环上删一条边,这样它必然构成树,删掉的边一定是不能同时选择的。

我们分别强制两个端点其中一个点不选,然后进行 \(dp\) ,相邻点不能选。

\(f[i][0/1]\) 表示以 \(i\) 为根节点的子树选/不选 \(i\) 所能获得的最大价值。

则有方程:

\[f[i][0]=\sum_{son_i} (max(f[son][0],f[son][1])) \]

\[f[i][1]=\sum_{son_i} (f[son][0]) \]

直接写即可,感觉还挺简单的。

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
const int N=2e6+5;

int head[N],ver[N],tot,nxt[N],val[N];
int vis[N],fa[N],n,root;
ll ans,f[N][2];

void add(int x,int y){
    ver[++tot]=y; nxt[tot]=head[x]; head[x]=tot;
}
void dp(int x){
    vis[x]=1;
    f[x][0]=0,f[x][1]=val[x];
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y!=root){
            dp(y); f[x][0]+=max(f[y][1],f[y][0]); 
                   f[x][1]+=f[y][0];
        }
        else f[y][1]=-N;
    }
}
inline void find_circle(int x){
    vis[x]=1;
    root=x;
    while(!vis[fa[root]]){
        root=fa[root];
        vis[root]=1;
    }
    dp(root);
    ll t=max(f[root][0],f[root][1]);
    vis[root]=1; root=fa[root];
    dp(root);
    ans+=max(t,max(f[root][0],f[root][1]));    
}
int main(){
    cin>>n;
    for(int i=1,x;i<=n;i++){
        cin>>val[i]>>x; 
        // scanf("%d%d",&val[i],&x); 
        add(x,i); fa[i]=x;
    }
    for(int i=1;i<=n;i++) if(!vis[i]) find_circle(i);
    cout<<ans<<endl;
    system("pause");
    return 0;
}
posted @ 2021-09-22 21:15  Evitagen  阅读(51)  评论(0编辑  收藏  举报