刷题树形dpZJOI 骑士

Posted xwww666666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了刷题树形dpZJOI 骑士相关的知识,希望对你有一定的参考价值。

国王交给了你一个艰巨的任务,从所有的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力。

为了描述战斗力,我们将骑士按照1至N编号,给每名骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。

 

因为一个骑士有且只有一个最讨厌的人(一条出边

而且这个骑士不会讨厌自己

即该图中是没有自环的

 

所以 考虑这个有向图

我们把x所讨厌的人y设为x的父亲节点

这样考虑每一个人都有且只有一条出边

所以对一个"联通块"

只有根节点有机会形成环(寻找到题目中图形的具体形状)

 

所以我们又解决了一个问题:

每个联通块内有且只有一个简单环

这样 我们考虑把每个联通块的环上删一条边

这样它必然构成树

 

然后要注意

删掉的边所连接的两点x,y

是不能同时选的

所以我们分别强制x,y其中一个点不选

对新树跑DP

显然相邻的点是不能选的

就是O(2n)

 

两个细节:

(1)vector上跑树超时 647ms -> 1.47s

(2)int会爆

#include<cstdio>
#include<cstdlib>
#include<vector>
#define ll long long 
using namespace std;
int n;
const int N=1e6+3;
int tot,head[N];
struct node
{
    int v,nx;
}e[N];
void add(int u,int v)
{e[++tot].v =v,e[tot].nx =head[u],head[u]=tot;}

int sz[N];
int fa[N],d[N];
bool vis[N];

ll f[N][2];
ll ans;int root;
void dfs(int rt)
{
    vis[rt]=true;
    f[rt][0]=0,f[rt][1]=d[rt];
    for(int i=head[rt];i;i=e[i].nx )
    {
        int v=e[i].v ;
        if(v==root) continue;
        
        dfs(v);
        f[rt][0]+=max(f[v][0],f[v][1]);
        f[rt][1]+=f[v][0];
    }
}
void find_circle(int rt)
{
    int r1,r2;
    vis[rt]=true;
    while(!vis[fa[rt]])
    {
        rt=fa[rt];
        vis[rt]=true;
    }
    r1=rt,r2=fa[rt];
    
    root=r1;
    dfs(r1);
    ll t=f[r1][0];
    root=r2;
    dfs(r2);
    t=max(t,f[r2][0]);
    
    ans+=t;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&d[i],&fa[i]),
        add(fa[i],i);
    for(int i=1;i<=n;i++)
        if(!vis[i])
            find_circle(i);
    printf("%lld
",ans);
    
    return 0;
}

 

然后我觉得复杂度太高,就进行了记忆化,

因为只有一个环路,两次dp路径差不多,所以没有影响的另外一支,不用重复求

647->547ms,考试还是稳妥一点好

#include<cstdio>
#include<cstdlib>
#include<vector>
#define ll long long 
using namespace std;
int n;
const int N=1e6+3;
int tot,head[N];
struct node
{
    int v,nx;
}e[N];
void add(int u,int v)
{e[++tot].v =v,e[tot].nx =head[u],head[u]=tot;}

int sz[N];
int fa[N],d[N];
bool vis[N];

int in[N];
ll f[N][2];
ll ans;int root;
void dfs(int rt)
{    
    if(in[rt]==2 ) return ;
    
    in[rt]=2;
    vis[rt]=true;
    f[rt][0]=0,f[rt][1]=d[rt];
    for(int i=head[rt];i;i=e[i].nx )
    {
        int v=e[i].v ;
        if(v==root) 
        {
            in[rt]=1;
            continue;
        }
        dfs(v);
        if(in[v]==1) in[rt]=1;
        
        f[rt][0]+=max(f[v][0],f[v][1]);
        f[rt][1]+=f[v][0];
    }
}
void find_circle(int rt)
{
    int r1,r2;
    vis[rt]=true;
    while(!vis[fa[rt]])
    {
        rt=fa[rt];
        vis[rt]=true;
    }
    r1=rt,r2=fa[rt];
    
    root=r1;
    dfs(r1);
    ll t=f[r1][0];
    root=r2;
    dfs(r2);
    t=max(t,f[r2][0]);
    
    ans+=t;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&d[i],&fa[i]),
        add(fa[i],i);
    for(int i=1;i<=n;i++)
        if(!vis[i])
            find_circle(i);
    printf("%lld
",ans);
    
    return 0;
}

 

以上是关于刷题树形dpZJOI 骑士的主要内容,如果未能解决你的问题,请参考以下文章

树形DPBZOJ1040-[ZJOI2008]骑士

ZJOI2008骑士[树形dp]

基环树/树形DPBZOJ1040-[ZJOI2008]骑士

骑士(树形dp)

BZOJ_1040_[ZJOI2008]骑士_树形DP

[ZJOI2008]骑士(基环树,树形dp)