题目解析
乍一想有(2^{2^n})种状态,直接暴力不太可取。
考虑树(dp)的话,设(dp[i])表示子树(i)的答案,由于有个(m)的限制,我们设(dp[i][j])表示子树(i)内有(j)个人选择作战的最大贡献。
但是这样很难转移,因为我们不知道儿子们选了啥。
但如果从儿子们的角度来考虑,若一个叶结点的所有祖先结点的状态确定,那么这个叶结点的贡献可以直接算出来。而每个叶结点有(2^{n-1})个祖先,如果枚举它们的祖先的状态,复杂度消耗只有(2^{n-1}),再乘上叶结点个数,时间复杂度是(2^{n-1} imes 2^{n-1}=2^n)。
所以相当于可以枚举一条链上的每个点的状态,用类似于状压的方式存状态,然后到了叶子结点可以转移上去。
(我在想这样枚举复杂度不是狂野的(2^{2^n})应该是因为,它是按照父子关系枚举下来的,按照每一条链的顺序枚举下来,左右儿子通过(dp)的方式合并答案,而不是像直接暴力那样,链与链之间通过枚举状态计算答案,(有点像背包那个意思),把链与链之间的复杂度从加法变成了乘法)
每个点还有转移(dp[u][i+j]=max(dp[u<<1][i]+dp[u<<1|1][j])),这里还有个(n)的复杂度,所以总时间复杂度应该是(O(n2^n))
►Code View
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
#define LL long long
#define N 15
#define K 5000
#define INF 0x3f3f3f3f
int rd()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48); c=getchar();}
return f*x;
}
int n,m,k,ans;
int w[K][N],f[K][N];//war farm
int dp[K][K];//以i为根的子树内有j个平民作战的最大贡献
void dfs(int u,int s,int d)//u为子树根节点 s为祖先的状态(状压 1作战 0后勤) d为子树大小
{
for(int i=0;i<=d;i++)
dp[u][i]=0;
//记得清空 下面的dp值在根选另一个选项时算过
if(d==1)
{//叶子结点
for(int i=0;i<n-1;i++)
{//枚举n-1个祖先 高位是比较老的祖先 爸爸是最低位
if((s>>i)&1)//第i个祖先作战
dp[u][1]+=w[u-(k-1)][i+1];
else dp[u][0]+=f[u-(k-1)][i+1];
}
return ;
}
for(int sit=0;sit<=1;sit++)
{//枚举当前为作战/后勤
dfs(u<<1,(s<<1)|sit,d>>1);
dfs(u<<1|1,(s<<1)|sit,d>>1);
for(int i=0;i<=min(d,m);i++)
for(int j=0;j<=min(d,m);j++)
dp[u][i+j]=max(dp[u][i+j],dp[u<<1][i]+dp[u<<1|1][j]);
}
}
int main()
{
n=rd(),m=rd();
k=1<<(n-1);//叶子结点个数
for(int i=1;i<=k;i++)
for(int j=1;j<=n-1;j++)
w[i][j]=rd();
for(int i=1;i<=k;i++)
for(int j=1;j<=n-1;j++)
f[i][j]=rd();
dfs(1,0,(1<<n)-1);
for(int i=0;i<=m;i++)
ans=max(ans,dp[1][i]);
printf("%d
",ans);
return 0;
}