zoukankan      html  css  js  c++  java
  • 【刷题】【树形dp】ZJOI 骑士

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

    为了描述战斗力,我们将骑士按照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;
    }
  • 相关阅读:
    kmp模板
    2017 ACM/ICPC Asia Regional Shenyang Online transaction transaction transaction
    2017 ACM/ICPC Asia Regional Shenyang Online 12 card card card
    KMP
    最长不下降子序列
    codeforces round 433 D. Jury Meeting
    codeforces round 433 C. Planning 贪心
    hdu 5792 线段树+离散化+思维
    hdu 5792 树状数组+离散化+思维
    hdu 5791 思维dp
  • 原文地址:https://www.cnblogs.com/xwww666666/p/11708219.html
Copyright © 2011-2022 走看看