「SCOI2015」小凸玩密室

「SCOI2015」小凸玩密室

题目链接:#2009. 「SCOI2015」小凸玩密室 - 题目 - LibreOJ (loj.ac)


​ 由于题目中有这么一个条件:在点灯的过程中,要保证任意时刻所有被点亮的灯泡必须连通,在点亮一个灯泡后必须先点亮其子树所有灯泡才能点亮其他灯泡。请告诉他们,逃出密室的最少花费是多少。。所以我们容易想到树形 dp ,然后对子树进行合并。

​ 我们可以很容易设计这么一个状态:\(dp_{i,j,0/1}\) 表示点亮以 \(i\) 为根的子树,最后一个被点亮的灯泡是 \(j\)\(0/1\) 表示起点是否是当前节点。

​ 那么合并的转移式就是:

\[\begin{aligned} dp_{i,j,1}&=min_{j=le}^{mid}(dp_{rs,k,1}+(dis_{k,i}+lw_i)*a_{ls[i]}+dp_{ls,j,1}) \\ dp_{i,j,1}&=min_{j=mid+1}^{ri}(dp_{ls,k,1}+(dis_{k,i}+rw_i)*a_{rs[i]}+dp_{rs,j,1}) \\ dp_{i,j,0}&=min_{j=le}^{mid}(dp_{rs,k,0}+rw_i*a_i+lw_i*a_{ls[i]}+dp_{ls,j,1}) \\ dp_{i,j,0}&=min_{j=mid+1}^{ri}(dp_{ls,k,0}+rw_i*a_{rs[i]}+lw_i*a_i+dp_{rs,j,1}) \end{aligned} \]

​ 分两部分转移,一条是先走右子树再走左子树,一条条是先走左子树再走右子树。

\(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;
}
posted @ 2021-10-06 15:40  夜空之星  阅读(96)  评论(0编辑  收藏  举报