[洛谷P4253]小凸玩密室

220 篇文章 2 订阅
98 篇文章 0 订阅

题目

传送门 to luogu

思路一

注意到点灯的条件是很苛刻的。如果你仔细思考,就会明白:本质上,可以写成某一种 d p \tt{dp} dp

首先只考虑从根节点 1 1 1 开始点灯的情况。发现搞定 x x x 的子树之后,一定是去 x x x 的某个祖先的兄弟。

为什么呢?因为 x x x 的某一个祖先,先走到了 x x x 所隶属的儿子子树。“回溯”的时候,就不得不进入其中,继续点灯。

或者再换一种说法。我可以将树按照某一种形态排列,使得每个非叶子节点被点亮后,下一个被点亮的,是其左儿子。也就是说,我让 灯被点亮的顺序恰好为先序遍历的顺序

这样一来,当子树 x x x 内所有的灯都被点亮的时候,下一个到达的将是 x x x 的某个祖先的兄弟。

然后就可以 d p \tt{dp} dp 了(因为只有 log ⁡ n \log n logn 个祖先)。可以用 f ( x , j ) f(x,j) f(x,j) 来表示状态。

那么不是从 1 1 1 开始呢?例如我们是从图中的黑色点开始?

在这里插入图片描述
我们需要算一个红色的玩意儿。发现是从 x x x开始走到某个祖先。那么再 d p \tt{dp} dp 一次就好了。

有了这些,我们就可以用一个变量存储,一路上的“红色路径”(走到祖父节点处)即可。

代码

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
inline int readint(){
	int a = 0, f = 1; char c = getchar();
	for(; c<'0' or c>'9'; c=getchar())
		if(c == '-') f = -1;
	for(; '0'<=c and c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
void writeint(long long x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar(x%10+'0');
}

const int MaxN = 200005;
long long v[MaxN], a[MaxN]; int n;

void init(){
	n = readint();
	for(int i=1; i<=n; ++i)
		a[i] = readint();
	for(int i=2; i<=n; ++i)
		v[i] = readint();
}

long long dp[MaxN][20], depth[MaxN];
// 搞定x的子树(走到x的代价不计入),回到x的i级祖先的兄弟
void getDP(int x){
	if((x<<1) <= n) getDP(x<<1);
	if((x<<1|1) <= n) getDP(x<<1|1);
	/* 先递归处理左右儿子 */
	for(int j=0; j<20; ++j){
		if(not (x>>j)) break;
		int fa = x>>j, lca = fa>>1;
		/* 此时即是在求回到fa的兄弟的dp值 */
		if((x<<1) > n) // 叶子节点 
			dp[x][j] = (depth[x]+depth[fa^1]-(depth[lca]<<1))*a[fa^1];
		else if((x<<1|1) > n) // 只有左儿子 
			dp[x][j] = v[x<<1]*a[x<<1]+dp[x<<1][j+1];
		else /* 膝下有两子 */
			dp[x][j] = min(v[x<<1]*a[x<<1]+dp[x<<1][0]+dp[x<<1|1][j+1],
				v[x<<1|1]*a[x<<1|1]+dp[x<<1|1][0]+dp[x<<1][j+1]);
		/* '膝下有两子'走法唯二(此处以先走左儿子作例):
			1. 搞定左儿子,回到其0级祖先的兄弟处(即右儿子)
			2. 其右儿子被搞定,然后走回其j+1级祖先
		毕竟,x的j级祖先,就是son(x)的j+1级祖先 */
	}
}
long long f[MaxN][20], answer;
// 回到x的i+1级祖先 
void getF(int x){
	if((x<<1) <= n) getF(x<<1);
	if((x<<1|1) <= n) getF(x<<1|1);
	for(int j=0; j<20; ++j){
		if(not (x>>j)) break;
		int fa = x>>j>>1;
		if((x<<1) > n)
			f[x][j] = (depth[x]-depth[fa])*a[fa];
		else if((x<<1|1) > n)
			f[x][j] = v[x<<1]*a[x<<1]+f[x<<1][j+1];
		else
			f[x][j] = min(v[x<<1]*a[x<<1]+dp[x<<1][0]+f[x<<1|1][j+1],
				v[x<<1|1]*a[x<<1|1]+dp[x<<1|1][0]+f[x<<1][j+1]);
		// 与dp的递推没有差了许多,同样的道理
	}
}
void dfs(int x,long long sum){
// printf("PPL or2 (%d %lld)\n",x,sum);
	answer = min(answer,f[x][0]+sum);
	if((x<<1) > n)
		return ;
	if((x<<1|1) > n){ // 只有左儿子 
		dfs(x<<1,sum+v[x]*a[x>>1]);
	}else{
		dfs(x<<1,sum+f[x<<1|1][1]+v[x<<1|1]*a[x<<1|1]);
		dfs(x<<1|1,sum+f[x<<1][1]+v[x<<1]*a[x<<1]);
	}
}
void solve(){
	for(int i=2; i<=n; ++i) // 预处理深度
		depth[i] = depth[i>>1]+v[i];
	getDP(1); answer = dp[1][0];
	getF(1); dfs(1,0);
	writeint(answer);
	putchar('\n');
}

signed main(){
	// freopen("escape.in","r",stdin);
	// freopen("escape.out","w",stdout);
	init();
	solve();
	return 0;
}

思路二

状态定义:搞定哪一个子树,结束于哪一个点上。

复杂度,令人惊讶。对于第 k k k 层,其叶子总数是 O ( n ) \mathcal O(n) O(n) 的。一共只有 log ⁡ n \log n logn 层,所以状态数量是 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) 的。

其转移便极其简单了。

### 回答1: JavaScript密室逃生是一种基于Web技术的益智游戏,家需要在虚拟的密室中找到线索,解开谜题,最终逃离密室。以下是一个300字的回答。 在JavaScript密室逃生游戏中,家置身于一个虚拟的密室中,被困在其中,唯一的目标就是找到线索和解谜以逃离这个密室。这个游戏中使用了JavaScript编程语言,是基于Web技术实现的。家需要通过与密室内的不同元素进行交互,来发现隐藏的线索或者道具。 首先,家需要仔细观察密室的各个角落,寻找可疑的地方或物品。这可能包括墙壁上的画,桌子上的文件,甚至是地板上的纸片。在这些隐藏的地方,可能会找到与解谜相关的密码、数字或者提示。这些线索一般会以文本、图像或者其他形式展示出来。 得到线索后,家需要运用逻辑推理的能力来解开谜题。这可能需要家对密室中一些特殊的物品或者符号进行分析,并将其与之前获得的线索进行关联。例如,一把钥匙可能与某个柜子或门锁匹配,一串数字可能是解开某个密码的关键。 除了逻辑推理,家还可能需要运用一些基本的编程知识和技巧。在某些情况下,家可能需要修改或者编写一些JavaScript代码来解决难题。例如,通过代码改变某个元素的属性或者触发某个事件以解开机关。 通过不断地观察,探索和尝试,家最终可以找到真正的出口,并成功逃离密室。这个过程需要家的观察力、逻辑思维、编程能力和耐心。随着游戏的进行,家可能会面临越来越复杂的谜题挑战,因此,这个游戏也可以帮助家培养解决问题和团队合作的能力。 总之,JavaScript密室逃生是一个富有挑战性和乐趣的益智游戏。通过与虚拟的密室进行互动,家可以锻炼自己的观察力、逻辑推理和编程能力,同时也能欣赏到游戏设计师所创造的精心设计。 ### 回答2: JavaScript密室逃生是一个基于Web的逃生游戏,家需要运用JavaScript编程知识解决一系列难题,成功逃离密室。 在游戏中,家将置身于一个虚拟的密室中,需要寻找线索、解开谜题、找到隐藏的钥匙等,最终打开门离开房间。在这个过程中,家需要利用JavaScript编程语言的特性来实现互动和解谜。 首先,家需要了解和运用JavaScript的基本语法、变量、运算符和控制流程等。通过编写代码,可以展示隐藏的物品、触发事件,比如按下某个按钮会出现新的线索或打开隐藏的柜子。 其次,家可以利用JavaScript的DOM操作来改变网页元素的属性和样式,以便寻找和获取关键的提示信息。例如,通过改变某个元素的位置、隐藏或显示某个按钮等,来揭示隐藏的谜题答案或是寻找钥匙。 此外,家还可以利用JavaScript的事件处理器来监听用户的动作和操作,实现互动和触发相应的功能。例如,检测家鼠标点击事件,当家点击某个按钮或物品时触发相应事件,打开门或解锁某个装置。 总的来说,JavaScript密室逃生是一款结合了编程技巧和解谜元素的游戏。通过熟练运用JavaScript编程语言的各种特性,家可以通过解谜和互动的方式最终成功逃离密室。这不仅考验了家对JavaScript的熟练程度,还提高了逻辑思维、解决问题的能力。 ### 回答3: JavaScript密室逃生是一种基于JavaScript编程语言的解谜游戏,家需要在限定的时间内找到线索、解开谜题,最终逃离密室。 在游戏开始时,家将被锁在一个虚拟的密室内,需要通过与密室内的物品、地点进行交互,找到隐藏的线索和道具。家可以通过点击鼠标或键盘操作与游戏界面进行互动。 在这个游戏中,家需要利用JavaScript编程语言的特点,运用一些常用的方法和函数来解决谜题。例如,通过查看代码、调试程序、修改变量的值等方式来寻找线索,实现游戏中的各种任务。 游戏难度逐渐增加,家需要具备一定的编程基础和逻辑思维能力。而且,家在游戏中需要灵活运用编程知识,想出各种创新的解决方案,来解开谜题并逃离密室。 JavaScript密室逃生游戏不仅仅是一种娱乐方式,同时也是一种让人们更加熟悉和掌握JavaScript编程语言的途径。通过这个游戏,家能够在娱乐的同时提升自己的编程技巧和解决问题的能力。 总之,JavaScript密室逃生是一种结合编程与解谜的创新游戏,具有一定的娱乐性和教育意义,适合有一定编程基础的人群尝试挑战。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值