当前位置 : 主页 > 网页制作 > html >

[JLOI2015]战争调度【暴力+树形Dp】

来源:互联网 收集:自由互联 发布时间:2021-06-12
Online Judge :Bzoj4007,Luogu P3262 Label :暴力,树形Dp 题解 参考了这篇blog https://www.cnblogs.com/GXZlegend/p/8300883.html。 定义状态 \(dp[i][j]\) ,表示以i为根的子树中 有j个叶子节点打战 的收益。

Online Judge:Bzoj4007,Luogu P3262

Label:暴力,树形Dp

题解

参考了这篇blog https://www.cnblogs.com/GXZlegend/p/8300883.html。

定义状态\(dp[i][j]\),表示以i为根的子树中有j个叶子节点打战的收益。

由于是一棵标准的完全二叉树,所以转移时用类似背包的方式合并两棵子树的贡献。

能产生贡献的只有平民(叶子节点),所以递归到叶子时才能计算贡献,而这个贡献值还与祖先的选择有关,所以必须在之前递归的时候,给那些祖先是否打战打上标记——也就是暴力枚举非叶子节点的选择。

整个算法看似很暴力,但实际因为其特殊的树形结构,dfs函数的调用次数不会太多,详细证明可以看上面那个博客。下面是本地测试dfs调用次数的结果,可以看到当n<=10时,dfs不会调用太多次,而每次递归时合并子树的复杂度也不大,所以这个数据范围完全没问题。

n= dfs调用次数 1 1 2 5 3 21 4 85 5 341 ... ... 10 349525

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
int dp[1050][520],w[2][1050][11];
/*
dp[i][j]:i的子树中有j个叶子节点打战的收益 
w[0/1][i][j]:题目给定,叶子节点i(0:作战,1:后勤)时对其第j个祖先的贡献
mark[i]:对于叶子,i这个深度的祖先,是否选择去打战
*/
bool mark[12];
int n,m;
void dfs(int x,int dep){
    for(int i=0;i<=(1<<dep);i++)dp[x][i]=0;
    
    if(!dep){
        for(int i=1;i<=n;i++){
            if(mark[i])dp[x][1]+=w[0][x][i];
            else dp[x][0]+=w[1][x][i];
        }
        return;
    }
    
    mark[dep]=0,dfs(x<<1,dep-1),dfs(x<<1|1,dep-1);
    for(int l=0;l<=1<<(dep-1);l++)for(int r=0;r<=1<<(dep-1);r++){
        dp[x][l+r]=max(dp[x][l+r],dp[x<<1][l]+dp[x<<1|1][r]);
    }

    mark[dep]=1,dfs(x<<1,dep-1),dfs(x<<1|1,dep-1);
    for(int l=0;l<=1<<(dep-1);l++)for(int r=0;r<=1<<(dep-1);r++){
        dp[x][l+r]=max(dp[x][l+r],dp[x<<1][l]+dp[x<<1|1][r]);
    }
}
int main(){
    scanf("%d%d",&n,&m);n--;
    int leaf=1<<n;
    
    for(int o=0;o<=1;o++)for(int i=0;i<leaf;i++)for(int j=1;j<=n;j++){
        scanf("%d",&w[o][i+leaf][j]);
    }
    
    dfs(1,n);
    int ans=0;
    for(int i=0;i<=m;i++)ans=max(ans,dp[1][i]);
    printf("%d\n",ans);
    return 0;
}
网友评论