深探树形dp

  看到同学在写一道树形dp,好奇直接拿来写,发现很不简单。

如图,看上去是不是很像选课,没错这不是选课,升级版吧,多加了点东西罢了。简单却调了一晚上和一上午。

思路:很简单强联通分量+缩点+树形dp。直接莽啊,发现强联通分量不是很会求,码力不好一直调。然后开始缩点,这个缩点就分成的讲究了你咋么缩都行反正是一张无向图不过要注意最后图是一个连通图,每个节点都会直接和间接和0(人造源点相连。

然后树上dp即可。很简单。树上dp出锅了,一直调,然后改成二叉树dp,还是wa。

发现缩点GG了根本不能那样缩,然后考虑缩点的细节。然后实现码力好点就行了。简单。

然后发现直接树上背包和多叉树dp效果是一样的,下面代码dfs是多叉树dp,dfs1是直接树上dp,都可以ac。

#include<iostream>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<iomanip>
#include<algorithm>
#include<queue>
#include<vector>
#include<stack>
#include<cstdio>
#include<map>
#include<deque>
#include<set>
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int maxn=102;
int n,m;
int lin[maxn],ver[maxn],nex[maxn],len=0;
int v[maxn],w[maxn],dfn[maxn],low[maxn],c[maxn];
int st[1000],top=0,vis[maxn],num=0,cnt=0,ru[maxn],chu[maxn];
int cv[maxn],cw[maxn],f[maxn][600],ls[maxn],rx[maxn];//左儿子,右兄弟
int clin[maxn],cver[maxn],cnex[maxn],clen=0;
vector<int>q[maxn];
void add(int x,int y)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
}
void cadd(int x,int y)
{
    cver[++clen]=y;
    cnex[clen]=clin[x];
    clin[x]=clen;
}
void tarjan(int x)
{
    dfn[x]=low[x]=++num;
    st[++top]=x;vis[x]=1;
    for(int i=lin[x];i;i=nex[i])
    {
        int tn=ver[i];
        if(dfn[tn]==0)
        {
            tarjan(tn);
            low[x]=min(low[x],low[tn]);
        }
        else if(vis[tn]==1)low[x]=min(low[x],dfn[tn]);
    }
    if(dfn[x]==low[x])
    {
        cnt++;int y;
        do
        {
            y=st[top--];vis[y]=0;
            c[y]=cnt;q[cnt].push_back(y);
        }
        while(x!=y);
    }
}
void dfs(int i,int j)
{
    if(i==-1||j==0||f[i][j]>0)return;
    dfs(rx[i],j);
    f[i][j]=max(f[i][j],f[rx[i]>0?rx[i]:0][j]);
    int vx=j-cw[i];
    for(int k=0;k<=vx;k++)
    {
        dfs(ls[i],vx-k);
        dfs(rx[i],k);
        f[i][j]=max(f[i][j],f[ls[i]>0?ls[i]:0][vx-k]+f[rx[i]>0?rx[i]:0][k]+cv[i]);
    }
}
void dfs1(int x)
{
    for(int i=cw[x];i<=m;i++)f[x][i]=cv[x];
    for(int i=clin[x];i;i=cnex[i])
    {
        int tn=cver[i];dfs1(tn);
        for(int j=m-cw[x];j>=0;j--)
            for(int k=0;k<=j;k++)
                f[x][j+cw[x]]=max(f[x][j+cw[x]],f[x][j+cw[x]-k]+f[tn][k]);
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)w[i]=read();
    for(int i=1;i<=n;i++)v[i]=read();
    for(int i=1;i<=n;i++){int x;x=read();add(x,i);}
    for(int i=1;i<=n;i++)if(dfn[i]==0)tarjan(i);
    memset(rx,-1,sizeof(rx));memset(ls,-1,sizeof(ls));
    for(int x=1;x<=n;x++){cw[c[x]]+=w[x];cv[c[x]]+=v[x];}
    for(int x=1;x<=cnt;x++)
    {
        for(int j=0;j<q[x].size();j++)
        {
            int te=q[x][j];
            for(int i=lin[te];i;i=nex[i])
            {
                int tn=ver[i];
                if(f[x][c[tn]]==0&&x!=c[tn])
                {
                    f[x][c[tn]]=1;
                    rx[c[tn]]=ls[x];
                    ls[x]=c[tn];
                    ru[c[tn]]++;
                }
            }
        }
    }
    for(int i=1;i<=cnt;i++)if(ru[i]==0)rx[i]=ls[0],ls[0]=i;
    for(int x=0;x<=n;x++)
    {
        for(int i=lin[x];i;i=nex[i])
        {
            int tn=ver[i];
            if(c[x]==c[tn])continue;
            cadd(c[x],c[tn]);chu[c[tn]]++;
        }
    }
    for(int i=1;i<=n;i++)if(chu[c[i]]==0){cadd(c[0],c[i]),chu[c[i]]++;}
    memset(f,0,sizeof(f));
    //dfs(0,m);
    dfs1(0);
    printf("%d\n",f[0][m]);
    return 0;
}

 经过不懈的努力总是可以成功的,我觉得。金明的预算方案大家可能很熟不就是有依赖性背包么?

可其实我用了3种方法解他,看似很简单其实很锻炼人的耐力啊。

1,第一次拿到题目,不会啊,不会啊,然后考虑怎么来写。书上有代码开了一个临时数组来存储最优解。不是很喜欢,因为看的不是很懂。

#include<iomanip>
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<cstring>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int maxn=32002;
int q[66],a[maxn],f[maxn],w[66],v[66];
int n,m;
int main()
{
//    freopen("1.in","r",stdin);
    m=read();n=read();
    for(int i=1;i<=n;i++)
    {
        w[i]=read();v[i]=read();q[i]=read();
        v[i]=v[i]*w[i];
    }
    for(int i=1;i<=n;i++)
    {
        if(q[i]==0)
        {
            for(int j=1;j<=w[i];j++)a[j]=0;
            for(int j=w[i];j<=m;j++)a[j]=f[j-w[i]]+v[i];
            for(int j=1;j<=n;j++)if(q[j]==i)
                for(int k=m;k>=w[i]+w[j];k--)a[k]=max(a[k-w[j]]+v[j],a[k]);
            for(int j=1;j<=m;j++)f[j]=max(f[j],a[j]);
        }
    }
    printf("%d\n",f[m]);
    return 0;
}
View Code

反正看看就懂了,当时反正是懂了。不是很好理解吧,不知道为什么要这么做。

2.发现题目上有每件物品最多有两件附件考虑并查集加01背包,这样就简单了很多把所有情况都列举出来只要选或不选即可。

#include<iomanip>
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<cstring>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int maxn=32002;
struct bwy//闻道玉门犹被遮,应将性命逐轻车
{
    int w,v,w1,v1,w2,v2,w3,v3;
}t[66];
int f[maxn],n,m,len=0;
int main()
{
    //freopen("1.in","r",stdin);
    m=read();n=read();
    for(int i=1;i<=n;i++)
    {
        int x,y,z;
        x=read();y=read();z=read();
        if(z==0)t[i].w=x,t[i].v=x*y;
        else if(t[z].w1==0)t[z].w1=x+t[z].w,t[z].v1=x*y+t[z].v;
        else t[z].w2=x+t[z].w,t[z].v2=x*y+t[z].v;
        if(t[z].w2!=0){t[z].w3=t[z].w2+t[z].w1-t[z].w;t[z].v3=t[z].v2+t[z].v1-t[z].v;}
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=1;j--)
        {
            if(j>=t[i].w)f[j]=max(f[j-t[i].w]+t[i].v,f[j]);
            if(j>=t[i].w1)f[j]=max(f[j-t[i].w1]+t[i].v1,f[j]);
            if(j>=t[i].w2)f[j]=max(f[j-t[i].w2]+t[i].v2,f[j]);
            if(j>=t[i].w3)f[j]=max(f[j-t[i].w3]+t[i].v3,f[j]);
        }
    }
    printf("%d\n",f[m]);
    return 0;
}
View Code

其实就4种情况,全列举出来即可。

3.树形dp,很少再因为自己感动了,树形dp解出来了,当时在写这道题的时候学长说可以树形dp来写,然后暗想自己以后一定要写出来,写出选课的时候,立马找出金明的预算方案,写了一遍发现写不通,样例都过不去,提交10分,各种到处问学长,然后都不行,由于m过大转移的时候复杂度是nm^2的直接就炸掉了。再次放弃。。念念不忘,必有反响。预算苦学树形dp,开始总结树形依赖背包的方法,发现一种比选课更优的做法,但复杂度还是老样子,用了那种算法终于40分了,剩下的当然都是tle了,再次放弃终于学会了一种树上背包的dp方式,复杂度nm,a掉了这道题。。感动瞬间,我一定要把这种nm的做法牢牢记住!

#include<bits/stdc++.h>
#include<iostream>
#include<cmath>
#include<cstdio>
#include<ctime>
#include<cstring>
#include<string>
#include<queue>
#include<deque>
#include<vector>
#include<stack>
#include<map>
#include<algorithm>
#include<iomanip>
#include<set>
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(int x)
{
    if(x==0){putchar('0');putchar('\n');return;}
    if(x<0)x=-x,putchar('-');
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int maxn=100;
int n,m;
int lin[maxn<<1],nex[maxn<<1],ver[maxn<<1],len=0;
int w[maxn],f[62][32000],v[maxn];
void add(int x,int y)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
}
void dfs(int x)
{
    for(int i=lin[x];i;i=nex[i])
    {
        int tn=ver[i];
        for(int j=1;j<=m;j++)f[tn][j]=f[x][j];
        dfs(tn);
        for(int j=m;j>=w[tn];j--)
            f[x][j]=max(f[x][j],f[tn][j-w[tn]]+v[tn]);
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    m=read();n=read();
    for(int i=1;i<=n;i++)
    {
        int x;
        w[i]=read();
        v[i]=read()*w[i];
        x=read();
        add(x,i);
    }
    dfs(0);
    printf("%d\n",f[0][m]);
    return 0;
}
View Code

有的时候就是需要那么一点坚持和执着。

posted @ 2018-12-07 12:32  chdy  阅读(274)  评论(0编辑  收藏  举报