P7587 [COCI2012-2013#1] MARS 题解

P7587 [COCI2012-2013#1] MARS 题解


知识点

线性 DP,位运算,二叉树。


题意分析

给定一棵深度为 \(K+1\) 的满二叉树,其中叶节点从 \(1\) 编号到 \(2^{K}\),可以随意地交换每个节点的左右子节点。

取出叶节点的排列 \(A_1,A_2,\ldots,A_{2^K}\),规定其权值为 \(\sum_{i=1}^{2^K-1} B_{A_i,A_{i+1}}\),其中 \(B\) 是给定的数组。

求能够得到的最小叶节点排列权值。


思路分析

部分分

首先,我们能够简单的得到一个略显暴力的想法:用类似区间 DP 的方式记一个 \(f_{i,j}\),表示左、右端点分别为 \(i,j\) 的情况下,该段区间的权值最小值,转移也十分简单。

直接做一个 DFS,暴力选取左右子节点的后代区间,然后累加即可:

\[f_{li,rj} = \min_{lj \in ls_{u},ri\in rs_{u}} (f_{li,lj} + B_{lj,ri} + f_{ri,rj}) \\ \forall li \in ls_u \land rj \in rs_u \lor li \in rs_u \land rj \in ls_u \\ \]

复杂度大概 \(O(2^{4K})\),卡卡能拿到 \(K \le 8\) 的点,但 \(K = 9\) 的点是完全过不了。

正解

\(O(2^{3K})\)

我们考虑优化上面的解法。

发现对于同一对 \(lj,rj\)\(B_{lj,ri}+f_{ri,rj}\) 的使用有大量的重复,那么我们可以直接预处理出来 \(g_{lj,rj}\),表示 \(g_{lj,rj} = \min_{ri} (B_{lj,ri}+f_{ri,rj})\),转移时直接调用,复杂度降为 \(O(2^{3K})\)

由于有效状态较少,这种方法是可以过的。

\(O(2^{2K}K)\)

如果我们把 \(K\) 的范围提高到 \(K \le 11\) 呢?

上面的方法明显就不行了,我们需要换一个状态。

我们考虑一个个加入叶节点,我们发现,当加入节点数上一个节点已知时,那么下一个能加入的节点范围是固定且连续的,它的长度取决于加入节点数范围区间取决于上一个节点,举个例子:

一共有 \(8\) 个叶节点,已经加入了 \(6\) 个,现在加入第 \(7\) 个,设加入的点的数列为 \(A_1,A_2,\ldots,A_{8}\),我们可以按二叉树左右子节点的分法来划分区间:\(\{ \{A_1,A_2,A_3,A_4\},\{ \{A_5,A_6\},\{A_7,A_8\} \} \}\),那么很明显,放在 \(A_7\) 这个位置的点只能是与 \(A_6\) 的最近公共祖先为 \(A_6\) 的父节点的父节点的点。

我们可以依照这个性质,运用位运算来快速解决这个问题:在到每一位的时候,枚举上一位数,求得范围后再枚举下一位数,最后简单统计即可。

在复杂度方面,刚刚说到:当加入节点数上一个节点已知时,那么下一个能加入的节点范围是固定且连续的,它的长度取决于加入节点数范围区间取决于上一个节点。我们依照这个性质,可以得到:

  1. 枚举上一位是 \(O(2^K)\) 级别的。
  2. 枚举位置以及范围区间大小是 \(O(\sum_{i=1}^K 2^{K-i}2^i)\),即 \(O(2^K K)\)

相乘即:\(O(2^{2K}K)\)


CODE

\(O(2^{3K})\)

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define Vi vector<int>
#define Vii vector<Vi>
#define tomin(a,b) ((a)=min((a),(b)))
#define FOR(i,a,b) for(int i=(a);i<=(int)(b);++i)
#define Range(j,i) FOR(j,i<(siz>>1)?(siz>>1):0,i<(siz>>1)?siz-1:(siz>>1)-1)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
constexpr int N=(1<<9)+1;
int l,n,idx,ans=INF;
int dfn[N];
int a[N][N];
#define lu (u<<1)
#define ru (u<<1|1)
#define siz (1<<(l-dep-1))
void dfs(int u,int dep,Vii &f){
	if(dep==l-1)return dfn[u]=++idx,++idx,f[0][1]=a[dfn[u]][dfn[u]+1],f[1][0]=a[dfn[u]+1][dfn[u]],void();
	if(dep==l)return f[0][0]=0,dfn[u]=++idx,void();
	Vii ls(siz,Vi(siz,INF)),rs(siz,Vi(siz,INF)),mi(siz<<1,Vi(siz<<1,INF));
	dfs(lu,dep+1,ls),dfs(ru,dep+1,rs),dfn[u]=dfn[lu];
	FOR(ri,0,siz-1)Range(rj,ri){
		if(rs[ri][rj]<INF)FOR(lj,0,siz-1)tomin(mi[lj][rj+siz],a[dfn[lu]+lj][dfn[ru]+ri]+rs[ri][rj]);
		if(rs[rj][ri]<INF)FOR(lj,0,siz-1)tomin(mi[rj+siz][lj],rs[rj][ri]+a[dfn[ru]+ri][dfn[lu]+lj]);
	}
	FOR(li,0,siz-1)Range(lj,li){
		if(ls[li][lj]<INF)FOR(rj,0,siz-1)tomin(f[li][rj+siz],ls[li][lj]+mi[lj][rj+siz]);
		if(ls[lj][li]<INF)FOR(rj,0,siz-1)tomin(f[rj+siz][li],mi[rj+siz][lj]+ls[lj][li]);
	}
	ls.clear(),rs.clear();
}
#undef lu
#undef ru
#undef siz
signed main(){
	cin>>l,n=1<<l;
	FOR(i,1,n)FOR(j,1,n)cin>>a[i][j];
	Vii f(1<<l,Vi(1<<l,INF));
	dfs(1,0,f);
	FOR(i,0,(1<<l)-1)FOR(j,0,(1<<l)-1)tomin(ans,f[i][j]);
	cout<<ans<<endl;
	return 0;
}

\(O(2^{2K}K)\)

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define tomin(a,b) ((a)=min((a),(b)))
#define FOR(i,a,b) for(int i=(a);i<=(int)(b);++i)
#define RCL(a,b,c,d) memset((a),(b),sizeof(c)*(d))
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
namespace IOstream{
#define getc() getchar()
#define putc(c) putchar(c)
#define blank(c) ((c)==' '||(c)=='\n'||(c)=='\r'||(c)==(EOF))
#define isdigit(c) (('0'<=(c)&&(c)<='9'))
	template<class T>inline void rd(T &x){
		static bool sign(0);
		static char ch(0);
		for(x=0,sign=0,ch=getc();!isdigit(ch);ch=getc())if(ch=='-')sign^=1;
		for(;isdigit(ch);x=(x<<1)+(x<<3)+(ch^'0'),ch=getc());
		return x=sign?-x:x,void();
	}
#undef blank
#undef digit
}using namespace IOstream;
constexpr int N=(1<<9)+10;
int k,n,ans=INF,*f,*g;
int a[N][N],DP[2][N];
signed main(){
	rd(k),n=1<<k;
	FOR(i,0,n-1)FOR(j,0,n-1)rd(a[i][j]);
	f=DP[1],g=DP[0],RCL(f,INF,int,n);
	for(int i=1,low=i&-i;i<n;++i,low=i&-i,swap(f,g),RCL(f,INF,int,n))
		FOR(j,0,n-1)FOR(k,(j^low)&~(low-1),((j^low)&~(low-1))+low-1)tomin(f[k],g[j]+a[j][k]);
	FOR(i,0,n-1)tomin(ans,g[i]);
	printf("%d\n",ans);
	return 0;
}

posted @ 2024-08-27 22:00  Add_Catalyst  阅读(24)  评论(0)    收藏  举报