首先 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;
}
标签:lf,子树,nn,省选,题解,mx,mathcal,联考,dp
From: https://www.cnblogs.com/zifanoi/p/18064499