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,暴力选取左右子节点的后代区间,然后累加即可:
复杂度大概 \(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\) 的父节点的父节点的点。
我们可以依照这个性质,运用位运算来快速解决这个问题:在到每一位的时候,枚举上一位数,求得范围后再枚举下一位数,最后简单统计即可。
在复杂度方面,刚刚说到:当加入节点数和上一个节点已知时,那么下一个能加入的节点范围是固定且连续的,它的长度取决于加入节点数,范围区间取决于上一个节点。我们依照这个性质,可以得到:
- 枚举上一位是 \(O(2^K)\) 级别的。
- 枚举位置以及范围区间大小是 \(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;
}

浙公网安备 33010602011771号