首页 > 其他分享 >迷宫守卫 题解

迷宫守卫 题解

时间:2024-07-15 21:41:30浏览次数:11  
标签:子树 守卫 int 题解 迷宫 pos 花费 cost lim

给个题目链接:迷宫守卫

下面直接开始讲了。

发现一个事情,省选的题已经不怎么考板子难度很高的题了,现在考的都是思维难度非常高的题。

首先,我们考虑字典序的性质,如果第一位劣,那么后面无论多优都没用,所以我们要优先满足靠前的位置。

于是我们考虑使用二分来找出第一个数,后面以此类推。每次对于每个比当前二分答案更小的叶子要至少选择一个点让他拐到其他路上,我们可以使用树形 \(dp\) 来求出每一次代价。具体地,考虑左右子树,左子树无法改变优先级,而如果右子树的花费还不如直接在这个点强制左拐,那就直接左拐,否则在右子树内操作,感觉不是很好理解,所以放一下代码:

int get_cost(int u,int lim){
	if(u>=leaf)return a[u]<lim?inf:0;//如果比要求最小值还小,不合法
	int cl=get_cost(u<<1,lim);
	int cr=get_cost(u<<1|1,lim);
	return cl+min(cr,a[u]);
	//右边花费很大,那就在这个点直接强制向左
	//然后两个花费加一块 
}

然后说一下 \(dfs\) 函数,这个非常复杂,所以一部分一部分讲。

先考虑如果当前点是叶子,如果发现这个点的值小于我们要求的最小值,则我们需要操作一下,然后想一下为什么操作是有用的,如果是左子节点不满足要求那不就炸了吗?

再想一下我们的要求,我们能走到这个点的话,如果这个点不能满足要求还是个左儿子,那我们要么返回了不合法,要么就已经在上面往其他路拐弯了,所以这个叶子要么最后一步能拐弯,要么不能拐弯但满足要求。然后看一下代码:

if(u>=leaf){
	if(a[u]<lim)m-=a[u>>1];//需要提前拐弯
	stk[++top]=a[u];
	return;
}

接着说下面的部分。我们考虑维护当前节点为根的子树的编号最小的叶子(注意不是权值,编号是指的从左向右数的次序)和编号最大的叶子,可以发现,叶子是连续的(废话),然后我们记录这些叶子的全职和编号。

接下来我们按照权值给他们排个序,开始二分。代码:

int l=u,r=u,len=0;
while(l<leaf)l=l<<1;
while(r<leaf)r=r<<1|1;
//找出这个子树的最左叶子和最右叶子,形成一段区间
for(int i=l;i<=r;i++){
	p[++len]={a[i],i};
}
sort(p+1,p+len+1);//按照每个点的值排序 

二分有点难理解。我们考虑在排序好的数组中二分,这里我们的 \(dfs\) 也记录了一个当前要求的最小数,所以我们二分时判断的数不能小于记录的数,取个 \(\operatorname{max}\) 就好了。

接下来考虑如果二分的值比 \(dfs\) 记录的值更大即更严格,我们就不用管,因为满足了二分要求。但是,如果记录值更大,则我们可以考虑只满足二分值,但是强制要求先向左子树走,最后的花费就是这俩玩意的最小值。

然后就是正常二分,判断花费超了以没调整边界,并记录最小花费和下一个点的位置。代码:

l=1,r=len;
int res=0,pos;
while(l<=r){//看最大能放在第一个数的是哪个,运用二分找出 
	int mid=l+r>>1;
	int cost=get_cost(u,max(p[mid].x,lim));
	if(u&1&&u>1&&p[mid].x<lim)cost=min(cost,get_cost(u,p[mid].x)+a[u>>1]);
	//是右儿子并且当前点不符合要求,所以可以强制左拐,然后再加上这个点需求的花费 
	if(cost<=m){
		l=mid+1;
		pos=p[mid].y;
		res=cost;//花费合法记录花费和位置
	}
	else r=mid-1;
}

然后我们因为是通过 \(dfs\) 走这棵树,故在他走完子树之后下一站肯定是他兄弟。但是我们又发现,为了满足当前子树的要求,兄弟子树未必取到最优,但是当前子树一定最优。所以我们考虑先减去当前子树的花费,因为这个花费必然要付。

然后把当前方案下兄弟子树的花费加回来,这里加的东西事实上就是对两个东西取个最小值:

  • 如果当前子树是左儿子,证明可能父亲节点采取强制措施,花费显然,否则是极大值,因为不可能强制向右。

  • 另外一个就是兄弟子树的第一个数优于当前子树的最小花费。

最后我们就一直这样走到根即可,代码:

m-=res;//扣掉花费
stk[++top]=lim=a[pos];//走到这个点
do{
	int now=(pos&1?inf:a[pos>>1]);//如果是右节点就不可能强制去,记一下已经用的花费
	m+=min(now,get_cost(pos^1,lim));//回溯时把这些花费加回去
	dfs(pos^1,lim);
	pos>>=1;//走向父亲 
}while(pos!=u);

剩下的部分没啥好说的,看一下代码应该就理解了:

#include<bits/stdc++.h> 
#define int long long
#define pii pair<int,int>
#define x first
#define y second
#define leaf (1<<n)
#define N 200005
#define inf 2e18
using namespace std;
int T,n,m,a[N],stk[N],top;
pii p[N];
int get_cost(int u,int lim){
	if(u>=leaf)return a[u]<lim?inf:0;//如果比要求最小值还小,不合法
	int cl=get_cost(u<<1,lim);
	int cr=get_cost(u<<1|1,lim);
	return cl+min(cr,a[u]);
	//右边花费很大,那就在这个点直接强制向左
	//然后两个花费加一块 
}
void dfs(int u,int lim){//lim是要求的最小值 
	if(u>=leaf){
		if(a[u]<lim)m-=a[u>>1];//需要提前拐弯
		stk[++top]=a[u];
		return;
	}
	int l=u,r=u,len=0;
	while(l<leaf)l=l<<1;
	while(r<leaf)r=r<<1|1;
	//找出这个子树的最左叶子和最右叶子,形成一段区间
	for(int i=l;i<=r;i++){
		p[++len]={a[i],i};
	}
	sort(p+1,p+len+1);//按照每个点的值排序 
	l=1,r=len;
	int res=0,pos;
	while(l<=r){//看最大能放在第一个数的是哪个,运用二分找出 
		int mid=l+r>>1;
		int cost=get_cost(u,max(p[mid].x,lim));
		if(u&1&&u>1&&p[mid].x<lim)cost=min(cost,get_cost(u,p[mid].x)+a[u>>1]);
		//是右儿子并且当前点不符合要求,所以可以强制左拐,然后再加上这个点需求的花费 
		if(cost<=m){
			l=mid+1;
			pos=p[mid].y;
			res=cost;//花费合法记录花费和位置
		}
		else r=mid-1;
	}
	m-=res;//扣掉花费
	stk[++top]=lim=a[pos];//走到这个点
	do{
		int now=(pos&1?inf:a[pos>>1]);//如果是右节点就不可能强制去,记一下已经用的花费
		m+=min(now,get_cost(pos^1,lim));//回溯时把这些花费加回去
		dfs(pos^1,lim);
		pos>>=1;//走向父亲 
	}while(pos!=u);
}
void solve(){
	top=0;
	cin>>n>>m;
	for(int i=1;i<leaf*2;i++){
		cin>>a[i];
	}
	dfs(1,0);
	for(int i=1;i<=top;i++){
		cout<<stk[i]<<' ';
	}
	cout<<'\n';
}
signed main(){
	cin>>T;
	while(T--){
		solve();
	}
	return 0;
}

好了,\(2023\) 省选的迷宫守卫就讲完了,感觉难度挺高的,而且代码细节挺多的,所以建议先理解一遍,然后自己尝试补全一下细节。

标签:子树,守卫,int,题解,迷宫,pos,花费,cost,lim
From: https://www.cnblogs.com/zxh923aoao/p/18303955

相关文章

  • 题解:SP11469 SUBSET - Balanced Cow Subsets
    SP11469(折半搜索)题目大意:给出$N$个数,求可以被分成两个和相等的集合的子集数。思路分析:首先考虑朴素的DFS,每个数有三种情况:选为$A$集合,选为$B$集合,两个集合都不选。暴力DFS时间复杂度为$3^{20}$。观察到$N$很小,而$3^{10}$是可以通过本题的,于是考虑折半搜索。我......
  • P10720 [GESP202406 五级] 小杨的幸运数字 题解
    题意如果一个数的质因子中只有两个不同的数则输出\(1\),否则输出\(0\)。思路从第一个质因子遍历到\(sum\)的话很明显是\(O(nt)\)最大是\(n^{10}\)很明显会炸掉。所以遍历到\(sum\)是不行的,考虑正整数\(n\)最大的质因数是\(\sqrt{n}\)所以遍历到\(\sqrt{n}\)即......
  • 题解:SP10502 VIDEO - Video game combos
    大意构造一个长度为\(k\)(\(k\)是给定的)的串\(x\),使得对于\(∀1\leqi\leqn,s_i\)在\(x\)中的出现次数之和最大。输出这个最大值。思考考虑对\(s_i\)建AC自动机,然后dp。记\(dp[i][u]\)表示为长度为\(i\)的字符串,且当前已计算的节点是Trie上的编号为\(u......
  • 【题解】 [CSP-J 2021 T1] 分糖果
    题目描述题目大意给定正整数\(n\)、\(L\)、\(R\),求\(\max_{i\in\left[L,R\right]}{i\bmodn}\)。思路题目主要考察:分类讨论。众所周知,对于\(\forallx\),有$(x\bmodn)\in\left[0,n-1\right]$。可以分为两种情况讨论:如果\(\left\lfloor\frac{L......
  • 题解:CF1833F Ira and Flamenco
    思路因为要一个长度为\(m\)的,且最大与最小的元素之差小于等于\(m\)所以序列应为\(a_i,a_i+1,a_i+2\dots,a_i+m-1\),所以满足要求的序列之需要连续\(m\)个就行了,这个最开始排序,去重后用lower_bound求一下小于\(a_i+m-1\)的数有没有\(m\)个就行了。考虑满足要求序列的......
  • P8704 [蓝桥杯 2020 省 A1] 填空问题 题解
    题目传送门A.跑步训练我们经过仔细观察,可以发现每222分钟就会消耗300300......
  • P2754 [CTSC1999] 家园 / 星际转移问题题解
    开始时,将源点连一条权值为\(k\)的边到地球。然后以时间分层,从上一层的点连接到下一层的点,权值为飞船载人数量,并将代表月球的点连接到汇点。每加一层,在上一层的基础上进行增广,看能不能增加流量,如果流量变为\(k\),那么证明运送完成。可以证明枚举时间最多到\(1500\),图的点数不......
  • SP4063 MPIGS - Sell Pigs / P4638 [SHOI2011] 银行家题解
    考虑使用网络流。建立源点\(S\)和汇点\(T\)。每个人作为一个点,将它们与汇点\(T\)连接,权值为需要的猪的数量。然后对于每个人,如果和之前的某个人开了相同的猪圈,那么就将之前的那个人的点与这个人的点连接。如果猪圈还没有被开过,就从源点\(S\)连接这个点,权值为猪圈猪的初......
  • P1402 酒店之王题解
    考虑使用网络流。分为\(6\)层。第一层为源点。第二层为所有菜的点。第三层和第四层都表示人。(限制只能选择一个)。第五层为房子。第六层为汇点。#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;constintN=410,M=101000,INF=0x3f3f3f3......
  • AE莫名的小问题解决办法和基础的操作快捷键分享
    更多macOS实用教程,小白教学点击这里!AdobeAfterEffects,简称AE,是由Adobe公司开发的视频剪辑和设计软件。它是一款用于动画、视觉效果和电影合成的二维半动画软件,广泛应用于电影、电视和网络视频创作。AfterEffects主要用于创建动态图像和视觉特效,被誉为制作动态影像设计不可或......