小凸玩密室

一、题目

点此看题

二、解法

在讲正解之前,这道题要踩三个点:
1、这棵树是一颗完全二叉树,所以很多地方可以简化。
2、要求已经点亮的灯要联通,感性理解就是灯堆在一起。
3、要求点亮子树之后才能点亮其他点,这就启示我们可以用树 d p dp dp
0x01 暴力
第一种方法就是全排,但是检查很麻烦,所以不推荐,还是可以拿到 10 p t s 10pts 10pts
更好的暴力避免了检查,枚举起点后暴力枚举子树内的情况,时间复杂度 O ( 2 n . . . ) O(2^n...) O(2n...),拿到 20 p t s 20pts 20pts
然后就可以跳过这道题了
0x02 正解
考虑树形 d p dp dp,设 d p [ u ] [ i ] dp[u][i] dp[u][i]为从 u u u点出发(假设 u u u点已点亮,不考虑花费),把子树全部点亮,然后从 i i i点结束的最小花费(因为我们需要算距离),状态转移为:
a [ i ] a[i] a[i]为点 i i i的权值, b [ i ] b[i] b[i]为父亲到 i i i的边权, d i s dis dis为距离)

{ d p [ u ] [ i ] = d p [ 2 u ] [ j ] + a [ 2 u ] × b [ 2 u ] + ( d i s [ u ] [ j ] + b [ 2 u + 1 ] ) × a [ 2 u + 1 ] + d p [ 2 u + 1 ] [ i ] d p [ u ] [ i ] = d p [ 2 u + 1 ] [ j ] + a [ 2 u + 1 ] × b [ 2 u + 1 ] + ( d i s [ u ] [ j ] + b [ 2 u ] ) × a [ 2 u ] + d p [ 2 u ] [ i ] \begin{cases} dp[u][i]=dp[2u][j]+a[2u]\times b[2u]+(dis[u][j]+b[2u+1])\times a[2u+1]+dp[2u+1][i]\\ dp[u][i]=dp[2u+1][j]+a[2u+1]\times b[2u+1]+(dis[u][j]+b[2u])\times a[2u]+dp[2u][i] \end{cases} {dp[u][i]=dp[2u][j]+a[2u]×b[2u]+(dis[u][j]+b[2u+1])×a[2u+1]+dp[2u+1][i]dp[u][i]=dp[2u+1][j]+a[2u+1]×b[2u+1]+(dis[u][j]+b[2u])×a[2u]+dp[2u][i]

第一个转移是当 i i i在右儿子时的转移,第二个是在左儿子的转移。
观察 d p dp dp式,发现对于每一个 i i i,对应的 j j j的取值是不变的,所以我们可以先算固定的那一部分,这样转移就能做到均摊 O ( 1 ) O(1) O(1),考虑可能的状态数,发现它很像线段树,每一层覆盖一遍线段(这里是叶子),所以状态数是 O ( n log ⁡ n ) O(n\log n) O(nlogn),时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

但是还有一个问题?怎么确定最初的起点,我们这么算只能知道起点为 1 1 1的答案。
还是考虑树形 d p dp dp,设 t [ u ] [ i ] t[u][i] t[u][i]为起点在子树 u u u内,把子树搞定,最后在 i i i作结的最小花费,我们并不需要关心起点具体是什么,因为我们不需要用起点来算答案,则有:

{ t [ u ] [ i ] = t [ 2 u ] [ j ] + d i s [ u ] [ j ] × a [ u ] + a [ 2 u + 1 ] × b [ 2 u + 1 ] + d p [ 2 u + 1 ] [ i ] t [ u ] [ i ] = t [ 2 u + 1 ] [ j ] + d p [ u ] [ j ] × a [ u ] + a [ 2 u ] × b [ 2 u ] + d p [ 2 u ] [ i ] \begin{cases} t[u][i]=t[2u][j]+dis[u][j]\times a[u]+a[2u+1]\times b[2u+1]+dp[2u+1][i]\\ t[u][i]=t[2u+1][j]+dp[u][j]\times a[u]+a[2u]\times b[2u]+dp[2u][i] \end{cases} {t[u][i]=t[2u][j]+dis[u][j]×a[u]+a[2u+1]×b[2u+1]+dp[2u+1][i]t[u][i]=t[2u+1][j]+dp[u][j]×a[u]+a[2u]×b[2u]+dp[2u][i]

发现这个式子可以用更上面一样的优化,时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn),由于我们没有考虑 u u u为根的情况,要用 d p dp dp来更新 t t t
还有一个问题,虽然状态数达不到,但是空间要开 n 2 n^2 n2的,所以我们用 v e c t o r vector vector来存状态,动态开状态,很多问题也能一并解决。

#include <cstdio>
#include <vector>
#define int long long
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
const int MAXN = 200005;
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,a[MAXN],b[MAXN],dep[MAXN];
vector<int> dp1[MAXN],dp2[MAXN],dis[MAXN];
void dfs(int u,int fa)
{
	if(2*u<=n)
	{
		dep[2*u]=dep[u]+b[2*u];
		dfs(2*u,u);//左子树 
		int t=dp1[2*u].size();//get size 
		if(2*u+1<=n)
		{
			dep[2*u+1]=dep[u]+b[2*u+1];//距离 
			dfs(2*u+1,u);//右子树 
			int Ml1=inf,Ml2=inf,Mr1=inf,Mr2=inf;
			for(int i=0;i<dp1[u].size();i++)//优化 
			{
				if(i<t)//看枚举的终点在左子树还是右子树 
				{
					Ml1=min(Ml1,dp1[2*u][i]+a[2*u]*b[2*u]+(dis[u][i]+b[2*u+1])*a[2*u+1]);
					Ml2=min(Ml2,dp2[2*u][i]+dis[u][i]*a[u]+b[2*u+1]*a[2*u+1]);
				}
				else
				{
					Mr1=min(Mr1,dp1[2*u+1][i-t]+a[2*u+1]*b[2*u+1]+(dis[u][i]+b[2*u])*a[2*u]);
					Mr2=min(Mr2,dp2[2*u+1][i-t]+dis[u][i]*a[u]+b[2*u]*a[2*u]);
				}
			}
			for(int i=0;i<dp1[u].size();i++)
			{
				if(i<t)//更新 
				{
					dp1[u][i]=Mr1+dp1[2*u][i];
					dp2[u][i]=min(dp1[u][i],Mr2+dp1[2*u][i]);
				}
				else
				{
					dp1[u][i]=Ml1+dp1[2*u+1][i-t];
					dp2[u][i]=min(dp1[u][i],Ml2+dp1[2*u+1][i-t]);
				}
			}
		}
		else//特判只有一个左儿子,这种情况只会有一种,且在最下面 
		{
			for(int i=u;i>=1;i/=2)//当前点可作为状态 
			{
				dp1[i].push_back(0);
				dp2[i].push_back(0);
				dis[i].push_back(dep[u]-dep[i]);
			}
			dp1[u][0]=a[2*u]*b[2*u];
			dp2[u][0]=dp1[u][0];
			dp1[u][1]=inf;
			dp2[u][1]=a[u]*b[2*u];
		}
	}
	else
	{
		for(int i=u;i>=1;i/=2)//动态开店 
		{
			dp1[i].push_back(0);
			dp2[i].push_back(0);
			dis[i].push_back(dep[u]-dep[i]);
		}
	}
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	for(int i=2;i<=n;i++)
		b[i]=read();
	dfs(1,0);
	int ans=inf;
	for(int i=0;i<dp2[1].size();i++)
		ans=min(ans,dp2[1][i]);
	printf("%lld\n",ans);
}

自闭了,我树形还是太差了,一定要先看懂题在想。

tc:我读完3遍题就可以口算了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值