JLOI2015 战争调度【树形dp】

题目链接

题目解析

乍一想有(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;
}
原文地址:https://www.cnblogs.com/lyttt/p/14011601.html