「SCOI2015」小凸玩密室
「SCOI2015」小凸玩密室
题目链接:#2009. 「SCOI2015」小凸玩密室 - 题目 - LibreOJ (loj.ac)
由于题目中有这么一个条件:在点灯的过程中,要保证任意时刻所有被点亮的灯泡必须连通,在点亮一个灯泡后必须先点亮其子树所有灯泡才能点亮其他灯泡。请告诉他们,逃出密室的最少花费是多少。。所以我们容易想到树形 dp ,然后对子树进行合并。
我们可以很容易设计这么一个状态:\(dp_{i,j,0/1}\) 表示点亮以 \(i\) 为根的子树,最后一个被点亮的灯泡是 \(j\)。\(0/1\) 表示起点是否是当前节点。
那么合并的转移式就是:
分两部分转移,一条是先走右子树再走左子树,一条条是先走左子树再走右子树。
\(le,ri\) 是这个节点管辖的最左叶子和最右叶子,\(mid\) 则是这个节点的左儿子管辖的最右端点。这些可以通过一个 dfs 预处理出来 。
这么开空间是 \(O(n^2)\) 的。但是我们发现这个一颗完全二叉树,而且每个节点访问的范围是 \(l_i,r_i\)。将同一层的节点所有的 \(l_i,r_i\) 合起来刚好是 \(n\) (应该是叶子个数) 个,而一共只有 \(\log n\) 层。所以我们对每层开一个空间为 \(n\) 的数组,总空间复杂度就降到了 \(O(n\log n)\)。
然后我们来分析一下时间复杂度,对于每个节点,可以在 \(O(cnt)\) 的时间内预处理出俩子树的最小 \(dp\) 值,然后 \(O(cnt)\) 进行转移( cnt 表示该节点管辖的叶子数目)。所以时间跟空间一样,复杂度为:\(O(n\log n)\)。
由于题目中所给的二叉树不是满二叉树,所以还可能出现有一个节点只有左儿子而没有右儿子的状况,由于这样的点最多存在一个,所以特判一下就好了。
代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 2e5+5;
const ll INF = 1e17;
int n,ls[MAXN],rs[MAXN],idx[MAXN],pos[MAXN],Siz;
ll dp[22][MAXN][2],a[MAXN],lw[MAXN],rw[MAXN],tmp[22][MAXN][2],dis[22][MAXN],le[MAXN],ri[MAXN];
void pre_dfs(int p,int dep)
{
if(!p) return ;
for(int i=1;i<dep;++i)
dis[i][p]=dis[i][p/2]+((p&1)?rw[p/2]:lw[p/2]);
if(!ls[p]&&!rs[p])
{
idx[++Siz]=p;
le[p]=ri[p]=Siz;
return ;
}
if(ls[p]) pre_dfs(ls[p],dep+1);
if(rs[p]) pre_dfs(rs[p],dep+1);
le[p]=min(le[ls[p]],le[rs[p]]),ri[p]=max(ri[ls[p]],ri[rs[p]]);
if(!rs[p]) le[p]=le[ls[p]],ri[p]=ri[ls[p]];
}
void dfs(int k,int dep)
{
int l=le[k],r=ri[k],mid=ri[ls[k]];
if(!ls[k]&&!rs[k])
{
dp[dep][l][1]=0;
dp[dep][l][0]=0;
return ;
}
if(ls[k]&&!rs[k])
{
dp[dep][l][1]=lw[k]*a[ls[k]];
dp[dep][0][0]=lw[k]*a[k];
dp[dep][l][0]=INF;
dp[dep+1][l][0]=dp[dep+1][l][1]=0;
return ;
}
dfs(ls[k],dep+1);dfs(rs[k],dep+1);
ll val[2];val[0]=val[1]=INF;
for(int i=l;i<=mid;++i)
{
val[1]=min(val[1],lw[k]*a[ls[k]]+dp[dep+1][i][1]+(dis[dep][idx[i]]+rw[k])*a[rs[k]]);
val[0]=min(val[0],min(dp[dep+1][i][1],dp[dep+1][i][0])+dis[dep][idx[i]]*a[k]+rw[k]*a[rs[k]]);
}
if(!rs[ls[k]]) val[0]=min(val[0],dp[dep+1][0][0]+lw[k]*a[k]+rw[k]*a[rs[k]]);
for(int i=mid+1;i<=r;++i)
{
dp[dep][i][1]=val[1]+dp[dep+1][i][1];
dp[dep][i][0]=val[0]+dp[dep+1][i][1];
}
val[0]=val[1]=INF;
for(int i=mid+1;i<=r;++i)
{
val[1]=min(val[1],rw[k]*a[rs[k]]+dp[dep+1][i][1]+(dis[dep][idx[i]]+lw[k])*a[ls[k]]);
val[0]=min(val[0],min(dp[dep+1][i][1],dp[dep+1][i][0])+dis[dep][idx[i]]*a[k]+lw[k]*a[ls[k]]);
}
if(!rs[rs[k]]) val[0]=min(val[0],dp[dep+1][0][0]+rw[k]*a[k]+lw[k]*a[ls[k]]);
for(int i=l;i<=mid;++i)
{
dp[dep][i][1]=val[1]+dp[dep+1][i][1];
dp[dep][i][0]=val[0]+dp[dep+1][i][1];
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
for(int i=2;i<=n;++i)
{
ll b;scanf("%lld",&b);
if(i%2==1) rs[i/2]=i,rw[i/2]=b;
else ls[i/2]=i,lw[i/2]=b;
}
pre_dfs(1,1);
dfs(1,1);
ll res=1e18;
for(int i=1;i<=Siz;++i)
for(int j=0;j<=1;++j)
res=min(res,dp[1][i][j]);
if(ls[1]&&!rs[1]) res=min(res,dp[1][0][0]);
printf("%lld\n",res);
return 0;
}