[省選聯考 2024] 迷宮守衛 題解

zifanwang發表於2024-03-10

首先 Bob 肯定是貪心操作,即如果能操作且右兒子中第一個數小於左兒子中的第一個數就一定操作(因為排列中的數兩兩不同),否則不操作。

考慮一個 dp,即 \(f_{i,j}\) 表示 \(i\) 中的子樹操作完以後使得第一個數為 \(j\) 的最小代價。發現總狀態數是 \(\mathcal O(2^nn)\) 的,對於一個點的轉移可以透過列舉左右子樹中最左邊的數來更新自己的狀態。

考慮怎麼最佳化,發現我們只需要知道左右兒子中最左邊的數的大小關係,可以將左、右兒子中的數從小到大排序,預處理出字首、字尾狀態的 \(\min\) 然後列舉較小值再二分一下即可做到 \(\mathcal O(2^nn^2)\)。如果使用歸併排序+掃描線可以做到 \(\mathcal O(2^nn)\)

考慮求完這個東西以後怎麼構造答案。先列舉出第一個數最大可以是多少,然後開始遞迴求解。

對於當前考慮的子樹,我們已經知道了最終的左邊第一個數是啥,那麼我們就可以知道這個數是在左子樹中還是右子樹中,也就是 Bob 有沒有在這個點操作。

求出使得 Bob 最終會這樣操作,在另一個子樹中需要花掉的最小魔力值,用剩餘的魔力值貪心操作最終在左邊的那個子樹,遞迴求解。然後再用剩下的魔力值遞迴求解另一個子樹。

時間複雜度 \(\mathcal O(2^nn\sim 2^nn^2)\)。我寫的是 \(\mathcal O(2^nn^2)\),能透過此題,最佳化有點太麻煩了。

參考程式碼:

#include<bits/stdc++.h>
#define ll long long
#define mxn 65538
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rept(i,a,b) for(int i=a;i<b;++i)
#define drep(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
int T,n,mx,a[mxn],sz[mxn<<1],p1[mxn<<1],p2[mxn<<1];
ll k,d[mxn],f1[mxn<<1],f2[mxn<<1],f3[mxn<<1];
vector<ll>dp[mxn<<1];
void dfs(int x,int lf){
    if(x>=1<<n){
        dp[x].resize(2);
        sz[x]=1,dp[x][1]=0;
        return;
    }
    dfs(x<<1,lf);
    dfs(x<<1|1,lf+sz[x<<1]);
    sz[x]=sz[x<<1]+sz[x<<1|1];
    dp[x].resize(sz[x]+1);
    rep(i,1,sz[x])dp[x][i]=1e18;
    rep(i,1,sz[x<<1])p1[i]=p2[i]=i;
    sort(p1+1,p1+sz[x<<1]+1,[&](int x,int y){return a[lf+x]<a[lf+y];});
    
    sort(p2+1,p2+sz[x<<1]+1,[&](int i,int j){
		return a[lf+sz[x<<1]+i]<a[lf+sz[x<<1]+j];
	});
	
    f1[0]=f2[sz[x<<1]+1]=f3[sz[x<<1]+1]=1e18;
    rep(i,1,sz[x<<1])f1[i]=min(f1[i-1],dp[x<<1|1][p2[i]]);
    drep(i,sz[x<<1],1){
    	f2[i]=min(f2[i+1],dp[x<<1|1][p2[i]]);
    	f3[i]=min(f3[i+1],dp[x<<1][p1[i]]);
	}
    rep(i,1,sz[x<<1]){
    	int l=1,r=sz[x<<1]+1;
    	while(l<r){
    		int mid=(l+r)>>1;
    		if(a[lf+sz[x<<1]+p2[mid]]>a[lf+i])r=mid;
    		else l=mid+1;
		}
		dp[x][i]=min(dp[x][i],dp[x<<1][i]+min(f2[l],f1[l-1]+d[x]));
	}
	rep(j,1,sz[x<<1]){
    	int l=1,r=sz[x<<1]+1;
    	while(l<r){
    		int mid=(l+r)>>1;
    		if(a[lf+sz[x<<1]+j]<=a[lf+p1[mid]])r=mid;
    		else l=mid+1;
		}
		dp[x][sz[x<<1]+j]=min(dp[x][sz[x<<1]+j],dp[x<<1|1][j]+f3[l]);
	}
}
ll solve(int x,int s,int lf,ll mx){
    if(x>=1<<n){
        printf("%d ",a[lf+1]);
        return 0;
    }
    if(s<=sz[x<<1]){
        ll mn=1e18;
        rep(i,1,sz[x<<1|1]){
            mn=min(mn,dp[x<<1|1][i]+(a[lf+sz[x<<1]+i]<a[lf+s]?d[x]:0));
        }
        ll cs=solve(x<<1,s,lf,mx-mn);
        mx-=cs;
        int m1=0;
        rep(i,1,sz[x<<1|1])if(dp[x<<1|1][i]+(a[lf+sz[x<<1]+i]<a[lf+s]?d[x]:0)<=mx)m1=max(m1,a[lf+sz[x<<1]+i]);
        rep(i,1,sz[x<<1|1])if(a[lf+sz[x<<1]+i]==m1){
            return cs+solve(x<<1|1,i,lf+sz[x<<1],mx-(a[lf+sz[x<<1]+i]<a[lf+s]?d[x]:0))+(a[lf+sz[x<<1]+i]<a[lf+s]?d[x]:0);
        }
    }else{
        ll mn=1e18;
        rep(i,1,sz[x<<1])if(a[lf+i]>a[lf+s]){
            mn=min(mn,dp[x<<1][i]);
        }
        ll cs=solve(x<<1|1,s-sz[x<<1],lf+sz[x<<1],mx-mn);
        mx-=cs;
        int m1=0;
        rep(i,1,sz[x<<1])if(a[lf+i]>a[lf+s]&&dp[x<<1][i]<=mx){
            m1=max(m1,a[lf+i]);
        }
        rep(i,1,sz[x<<1])if(a[lf+i]==m1){
            return cs+solve(x<<1,i,lf,mx);
        }
    }
}
void Solve(){
    scanf("%d%lld",&n,&k);
    rept(i,1,1<<n)scanf("%lld",&d[i]);
    rep(i,1,1<<n)scanf("%d",&a[i]);
    dfs(1,0);
    mx=0;
    rep(i,1,1<<n)if(dp[1][i]<=k&&a[i]>a[mx])mx=i;
    solve(1,mx,0,k);
    puts("");
}
signed main(){
    scanf("%d",&T);
    while(T--)Solve();
    return 0;
}