树形DP 枚举祖宗的例题

这类题目是真的很头疼....其实这类题目的特征也很明显,叶子结点贡献答案时和其所在链的祖宗有关,也就是说要想得知其贡献必须知道他的所有祖宗的贡献,其实处理方法也不是太难,就是在dfs枚举时顺便把祖宗的状态状压一下.

到叶子结点时统计答案,最后将答案上传就行了.

战争调度

这个算是这类题目最好的例题了.很显然,一个平民的贡献只和他的直系上属有关转化为图论的语言就是和他的所有祖宗有关.而非叶结点又不会贡献答案.

我们直接给每个非叶结点一个状态0/1表示其参与战争还是后勤,我们在dfs传参数时直接记录下所有的祖宗信息就行了.这样到叶子结点后,我们就能很轻松的统计答案了.

同时由于不超过m个人参与战争的限制,我们设dp[x][i]表示点x控制的平民在中有i个人参与战争的最大贡献,直接转移就行了.

//不等,不问,不犹豫,不回头.
//这类题真的好难搞啊.....
//首先要想统计一个子节点的贡献,必须知道他所有祖宗的状态,所以就有了这种做法,在dfs时一遍枚举
//当前结点的贡献一遍往下递归.这样当到最底层时,其所有祖宗的状态就已经知道了,其叶节点的贡献也就
//容易知道了.回溯时再向父结点转移. 
#include<bits/stdc++.h>
#define _ 0
#define db double
#define RE register
#define ll long long
#define P 1000000007
#define INF 1000000000
#define get(x) x=read()
#define PLI pair<ll,int>
#define PII pair<int,int>
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
#define pb(x) push_back(x)
#define ull unsigned long long
#define getc(c) scanf("%s",c+1)
#define put(x) printf("%d
",x)
#define putl(x) printf("%lld
",x)
#define rep(i,x,y) for(RE int i=x;i<=y;++i)
#define fep(i,x,y) for(RE int i=x;i>=y;--i)
#define go(x) for(int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const int N=2500;
int n,m,w[N][15],f[N][15],num,dp[N][N];

inline int read()
{
    int x=0,ff=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*ff;
}

inline void dfs(int x,int s,int sz)//1打仗,0后勤. 
{
    rep(i,0,sz) dp[x][i]=0;
    if(sz==1)
    {
        rep(i,0,n-2) if(!(s&1<<i)) dp[x][0]+=f[x][i+1];
        rep(i,0,n-2) if(s&1<<i)    dp[x][1]+=w[x][i+1];
        return;
    }
    rep(k,0,1)
    {
        dfs(x<<1,s<<1|k,sz>>1);dfs(x<<1|1,s<<1|k,sz>>1);
        rep(i,0|k,min(sz,m)) rep(j,0,i)
            dp[x][i]=max(dp[x][i],dp[x<<1][j]+dp[x<<1|1][i-j]);
    } 
}

int main()
{
    //freopen("1.in","r",stdin);
    get(n);get(m);num=1<<n-1;
    rep(i,num,num*2-1) rep(j,1,n-1) get(w[i][j]);
    rep(i,num,num*2-1) rep(j,1,n-1) get(f[i][j]);
    dfs(1,0,(1<<n)-1);
    int ans=0;
    rep(i,0,m) ans=max(ans,dp[1][i]);
    put(ans); 
    return (0^_^0);
}
//以吾之血,祭吾最后的亡魂

网络收费

这个真的算是秒题了,算是我写的第二道黑题吧!(虽然是照着题解一个字母一个字母抄的....)

首先我们要解决点对对答案的贡献,我们总不能在处理一个点时还知道其他点的状态吧!那样必然会很麻烦!

题目中的收费标准归纳一下就是:如果两个点对不同的话,代价为一个f,点对相同且为lca数量多的模式,则无代价.为lca数量少的模式贡献两个f.

这启示我们将点的贡献放到其lca上,且标记点那种模式较多,如果一个点模式与lca的模式不同,必然会造成一个f的代价!容易发现这和题意的限制相对应.

这样我们只需要一个一个统计点的贡献即可.之后就转换成和上一道题类似的模型.只不过细节更加繁琐......

//不等,不问,不犹豫,不回头.
//观察这道网络收费的题,如果我们将贡献放到非叶子节点上的话,进行dp就比较合理了
//首先观察题意可以归纳出以下结论:若两个叶结点不同,必然贡献f[i][j]的贡献,相同的话若状态为lca
//的多数叶子结点的状态的话,则无贡献.否则贡献为2*f[i][j].
//首先要解决的问题便是点对之间的贡献如何处理.因为这个限制我们在处理一个点时,必须考虑其他点
//的影响,这使得求解起来非常麻烦.考虑将两个点对的贡献放到其lca处.我们额外给非叶子结点状态0/1
//表示其子树内哪个状态比较多,0表示子树内1偏多,1表示子树内0偏多,如果一个叶子结点与其状态不同
//必然付出f的代价.否则无代价.发现这和题目的限制是相应的.之前一直不理解这个意思,现在恍然大悟.
//当状态不同时,他要累加上其他所有点的代价,因为不管另一个点是什么,他已经会造成1的代价了.
//至于另一个如果也造成代价的话,我们枚举另一个点时会计算上的.
//之后发现一个点的贡献与其所有的祖宗结点相关,和战争调度类似在dfs时直接带上祖宗的状态即可. 
#include<bits/stdc++.h>
#define _ 0
#define db double
#define RE register
#define ll long long
#define P 1000000007
#define INF 1000000000
#define get(x) x=read()
#define PLI pair<ll,int>
#define PII pair<int,int>
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
#define pb(x) push_back(x)
#define ull unsigned long long
#define getc(c) scanf("%s",c+1)
#define put(x) printf("%d
",x)
#define putl(x) printf("%lld
",x)
#define rep(i,x,y) for(RE int i=x;i<=y;++i)
#define fep(i,x,y) for(RE int i=x;i>=y;--i)
#define go(x) for(int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const int N=(1<<11)+10;
int dp[N][N],v[N][N],n,ori[N],cv[N],lq[N],rq[N];

inline int read()
{
    int x=0,ff=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*ff;
}

inline void dfs(int x,int l,int r,int s,int dep)
{
    rep(i,0,r-l+1) dp[x][i]=INF;
    if(l==r)
    {
        dep--;
        dp[x][0]=dp[x][1]=0;
        if(ori[l]) dp[x][1]=cv[l];//注意这里1指的是0的状态比1多,所以此时其实上x的状态为0. 
        else dp[x][0]=cv[l];
        rep(i,1,dep)
        {
            int mid=(lq[i]+rq[i])>>1;
            if(s&1<<dep-i) 
            {
                if(l<=mid) dp[x][0]+=v[l][rq[i]]-v[l][mid];
                else dp[x][0]+=v[l][mid]-v[l][lq[i]-1];
            }
            else
            {
                if(l<=mid) dp[x][1]+=v[l][rq[i]]-v[l][mid];
                else dp[x][1]+=v[l][mid]-v[l][lq[i]-1];
            }
        }
        return;
    }
    int mid=l+r>>1;
    int len=r-l+1;
    lq[dep]=l;rq[dep]=r;
    dfs(x<<1,l,mid,s<<1,dep+1);
    dfs(x<<1|1,mid+1,r,s<<1,dep+1);
    rep(i,0,len/2-1) rep(j,0,i) dp[x][i]=min(dp[x][i],dp[x<<1][j]+dp[x<<1|1][i-j]);
//注意这里0的个数只能限制为0 - len/2-1.因为我们这里的0状态实际上是文中的模式A.
//而上面我们已经强制让这个点选了状态0,说明此时1的状态多余0的状态,所以0的数量只能不足一半. 
    dfs(x<<1,l,mid,s<<1|1,dep+1);
    dfs(x<<1|1,mid+1,r,s<<1|1,dep+1);
    rep(i,len/2,len) rep(j,0,i) dp[x][i]=min(dp[x][i],dp[x<<1][j]+dp[x<<1|1][i-j]);
}

int main()
{
    //freopen("1.in","r",stdin);
    get(n);n=1<<n;//所有的叶子结点个数.
    rep(i,1,n) get(ori[i]);
    rep(i,1,n) get(cv[i]);
    rep(i,1,n-1) rep(j,i+1,n) get(v[i][j]),v[j][i]=v[i][j];
    rep(i,1,n) rep(j,1,n) v[i][j]+=v[i][j-1];
    memset(dp,0x3f,sizeof(dp));
    dfs(1,1,n,0,1);    
    int ans=INT_MAX;
    rep(i,0,n) ans=min(ans,dp[1][i]);
    put(ans);
    return (0^_^0);
}
//以吾之血,祭吾最后的亡魂

 

原文地址:https://www.cnblogs.com/gcfer/p/12933682.html