社论:『省选联考 2024』 迷宫守卫

0

签了五个小时到,望周知。

1

先猜了几个结论,全部假了。

然后发现如果没有花费的限制,答案就是排列本身。于是发现自己没办法搞清楚这个结构。

猜测答案形如,每次给能到达的最大的数铺路。写了第一个假做法。

尝试最大化第一个数,发现此后每次都相当于在一个空子树内最大化第一个数,特别有道理。

发现可以二分,记 \(f_u=f_{ls}+\min\{f_{rs},c_u\}\) dp 即可。写了第二个假做法。

发现最大化第一个数的时候可能在另一棵子树内,还要记录是否之前被钦定被唤醒。写了第三个假做法。

发现竟然有后效性,那咋办。发现如果这个数足够大,可以反悔根链上的若干个点。写了第四个假做法。

发现有了反悔之后就不能二分了,那咋办。先改成平方,直接从大往小枚举,这样总枚举量是 \(2^nn\) 的,每次还需要 dfs 整棵子树。对拍发现反悔得不够,如果新唤醒了一个点,右子树里的代价可以全部清空。发现这本质上是,对于当前最小化的子树,我们已经确定能够走到它了,所以可以直接清空子树内的所有之前唤醒的点,最后的答案一定是合法的。拿到 75pts。

然后发现从大往小枚举第一个数中,只有当前数的根链的 dp 值有变化,不需要每次都 dfs 一遍,这样复杂度就是 \(O(2^nn^2)\)​ 了。拿到 100pts。

然后发现所谓的“反悔根链上的若干个点”,实际上只有子树根这一个,好像又能二分了,但是常数可能更大。

2

代码
#include<bits/stdc++.h>
constexpr int rSiz=1<<21;
char rBuf[rSiz],*p1=rBuf,*p2=rBuf;
#define gc() (p1==p2&&(p2=(p1=rBuf)+fread(rBuf,1,rSiz,stdin),p1==p2)?EOF:*p1++)
template<class T>void rd(T&x){
	char ch=gc();
	for(;ch<'0'||ch>'9';ch=gc());
	for(x=0;ch>='0'&&ch<='9';ch=gc())
		x=(x<<1)+(x<<3)+(ch^48);
}
constexpr int _=131077;
constexpr long long inf=1e18;
long long Min(long long x,long long y){return x<y?x:y;}
int n,N,a[_],b[_];
long long m,c[_];
int f[_],siz[_],d[_];
long long t[_];
std::vector<int>e;
void dfs1(int u){
	t[u]=inf;
	if(u>=N)return e.push_back(a[u-N]),void();
	if(d[u])d[u]=0,m+=c[u];
	dfs1(u<<1);dfs1(u<<1|1);
}
void dfs2(int u){
	if(u>=N||t[u]>=inf)return;
	dfs2(u<<1);
	if(t[u]==t[u<<1]+c[u])d[u]=1;
	if(!d[u])dfs2(u<<1|1);
}
void solve(){
	rd(n),rd(m);N=(1<<n);
	for(int i=1;i<N+N;++i)siz[i]=d[i]=f[i]=0;
	for(int i=1;i<N;++i)rd(c[i]);
	for(int i=0;i<N;++i)rd(a[i]),b[a[i]]=i+N;
	int u=1;for(int i=0;i<N;++i){
		e.clear();dfs1(u);std::sort(e.begin(),e.end());
		int p=0;bool ok=(u&1)&&d[u>>1];
		if(ok)m+=c[u>>1],d[u>>1]=0;
		for(;;){
			p=e.back();e.pop_back();
			if(ok&&p<=f[u^1])m-=c[u>>1],d[u>>1]=0,ok=0;
			t[b[p]]=0;for(int y=b[p]>>1;y>=u;y>>=1)
				t[y]=t[y<<1]+Min(t[y<<1|1],c[y]);
			if(t[u]<=m)break;
		}
		m-=t[u];dfs2(u);printf("%d ",p);
		if(i+1==N)return putchar(10),void();
		for(int x=u=b[p];x;x>>=1)if((++siz[x])==1)f[x]=p;
		for(int j=2;;j=j<<1,u>>=1)if(siz[u>>1]<j){u^=1;break;}
	}
}
int main(){
	int T;rd(T);
	for(;T;--T)solve();
}
posted @ 2026-01-16 12:02  Jordan_Pan  阅读(5)  评论(0)    收藏  举报