P10220
提供一个和大众做法不一样的做法。
其实这个神秘做法是因为我想歪了,我如果一次走一步就和其他题解一样了,但我每次都直接走到一个叶子,然后性质很差。
和其他题解一样,我们先考虑 A 操作以后 B 第一个到达的点最大能是多少。
如果你唤醒了某个守卫,那这个守卫的右子树的点都被支配了,也就是会在这一轮无法被选中。
这个结构具有良好的性质,我们二分一个答案,然后用 dp 确定合法性。具体来说设
然后我们可以得到 B 第一个到达的点,设为
接下来我们希望第二个,第三个,以及后面的被访问的点也可以通过类似上面的方法去求出来。
我们模拟 B 走路的过程,走到一个叶子以后会往上,然后再往下到一个没有任何一个点被访问的子树,访问完这个子树的所有点以后,又会往上,然后又往下走到一个更大的没被访问的子树。
如果我们当前走到了一个没有任何点被访问的子树的根的位置,我们希望像一开始一样,求出 A 当前可以让 B 到达的最大的位置。
这个问题看似是上一个问题的子问题,但是实际上直接做并不对。因为你现在已经钦定了 B 必须要先到这个第一个到达的点
我们定义覆盖:如果
称一个最深的覆盖是对于一个
考虑我们钦定第一个走到的位置是
可以推广的更一般的情况,如果当前访问到了没有任何一个点被访问的子树的根(定义看上面 B 的走路过程),设其为
只考虑
换句话说就是,对于所有
现在继续考虑这样一个问题:已经确定了 B 前
我们把限制全部列出来,相当于对于每个点有一个最深覆盖的深度限制。
这个东西就可以 dp 了,设
转移每次枚举两边子树当前的限制,然后再枚举当前的点选不选。
可以用前缀优化做到
然后确定了前若干次 B 访问到的点,我们继续按照上面 B 走路的过程模拟。如果我们确定了下一个 B 走过的叶子节点,我们就直接让 B 走到这个叶子节点然后继续上面的过程。
根本上这个过程是考虑到因为后面走到点的不确定,守卫的激活状态是不确定的,但是 B 前面走的路径却是确定的,所以其对覆盖关系的限制也能确定。直接钦定 B 下一个经过的叶子节点然后用 dp 去判断合法性。
这个暴力做法的复杂度就先不分析了,每尝试确定下一个点就要做一次 dp,所以复杂度大概是
我们知道满二叉树的结构非常优秀,我们尝试优化上述的过程。
我们考虑到一个没有任何点被访问的子树的根
这个东西乍一看,每次有一个二分,然后有
考虑分析它。这个树上大小为
所以复杂度是
#include<bits/stdc++.h>
using namespace std;
#define double long double
#define lowbit(x) (x&(-x))
#define int long long
const int inf = 1000000000000000;
int n,K;
vector<int>son[200005],val[200005];
int ll[200005],rr[200005],dep[200005];
int dp[200005][18];
int vis[200005];
int a[200005],p[200005],pos[200005];
int b[200005],c[200005];
int f[20],g[20];
vector<int>ans;
int tot;
//已经确定的限制 正在 check 的限制
void build(int l,int r,int k){
ll[k] = l,rr[k] = r;
dep[k] = dep[k>>1]+1;
if(l == r){son[k].push_back(k);val[k].push_back(p[k]);return;}
int mid = l+r>>1;
build(l,mid,k<<1),build(mid+1,r,k<<1|1);
for(auto x:son[k<<1])son[k].push_back(x);
for(auto x:son[k<<1|1])son[k].push_back(x);
for(auto x:val[k<<1])val[k].push_back(x);
for(auto x:val[k<<1|1])val[k].push_back(x);
}
void work(int now){
if(now>=tot){
for(int i =0;i<=n;i++)dp[now][i]=inf;
dp[now][max(c[now],b[now])] = 0;
return;
}
work(now<<1),work(now<<1|1);
//钦定最大值在左边,什么都不用干
int mn = inf,d = dep[now];
for(int i = 0;i<d;i++){
mn = min(mn,dp[now<<1|1][i]);
dp[now][i] = dp[now<<1][i]+mn;
}
mn = min(mn,dp[now<<1|1][d]);
for(int i =0;i<d;i++){
dp[now][i] = min(dp[now][i],min(dp[now<<1][i]+mn+a[now],inf));
}
mn = inf;
//钦定最大值在右边
for(int i =0;i<d;i++){
mn = min(mn,dp[now<<1][i]);
dp[now][i] = min(dp[now][i],dp[now<<1|1][i]+mn);
}
//把右边的消掉
}
void slove(int now,int tp){
if(now==1)return;
//从下向上合并 now 和他的兄弟
int to = now^1;
//需要讨论 now 在左边还是右边
int mn = inf,d = dep[now>>1];
// cout << "! " << to << endl;
// for(int i =0;i<=d;i++)cout << dp[to][i] << " ";cout << endl;
for(int i = 0;i<d;i++)g[i]=inf;
for(int i = 0;i<d;i++){
mn = min(mn,dp[to][i]);
g[i] = f[i]+mn;
}
if(to&1){
mn = min(mn,dp[to][d]);
for(int i =0;i<d;i++){
g[i] = min(g[i],min(f[i]+mn+a[now>>1],inf));
}
}
mn = inf;
for(int i =0;i<d;i++){
mn = min(mn,f[i]);
g[i] = min(g[i],dp[to][i]+mn);
}
if(now&1){
mn = min(mn,f[d]);
for(int i =0;i<d;i++){
g[i] = min(g[i],min(dp[to][i]+mn+a[now>>1],inf));
}
}
for(int i =0;i<d;i++)f[i] = g[i];
if(tp){for(int i =0;i<d;i++)dp[now>>1][i] = f[i];}
slove(now>>1,tp);
}
bool check(int now,int v){
int d =dep[now];//cout << " " << d << endl;
for(auto x:son[now])if(p[x]<v)c[x] = d;
work(now);
for(int i = 0;i<d;i++)f[i] = dp[now][i];
for(int i = d;i<=n;i++)f[i] = inf;
slove(now,0);
for(auto x:son[now])c[x] = 0;
return (f[0]<=K);
}
void dfs(int now,int to = 0){
int l = ll[now],r = rr[now],d = dep[now];
//cout << now << " " << to << " " << l << " " << r << endl;
// to 是某个叶子在序列中的位置,如果没有就找一个
if(now>=tot){ans.push_back(p[now]);return;}
if(!to){
//否则在子树里面二分
int l = 0,r = val[now].size()-1;
while(l<r){
int mid = l+r+1>>1;
if(check(now,val[now][mid])){
l = mid;
}else r = mid-1;
}
to = pos[val[now][l]]+tot-1;
for(auto x:son[now])if(p[x]<val[now][l])b[x] = d;
work(now);
// cout << " > " << now << endl;
// for(int i =0;i<d;i++)cout << dp[now][i] <<" ";cout << endl;
for(int i =0;i<d;i++)f[i] = dp[now][i];
slove(now,1);
// cout << " " << now << " " << pos[val[now][l]] << endl;
}
int ppos = to-tot+1;
int mid = l+r>>1;
if(ppos<=mid){
dfs(now<<1,to);
dfs(now<<1|1,0);
}else{
dfs(now<<1|1,to);
dfs(now<<1,0);
}
}
void solve(){
cin >> n >> K;
tot = 1<<n;
for(int i =0;i<=tot*2;i++)son[i].clear(),val[i].clear();
ans.clear();
for(int i =0;i<=tot*2;i++)for(int j =0;j<=n;j++)dp[i][j] = 0;
for(int i =0;i<=tot*2;i++)vis[i] = b[i] = c[i]=0;
for(int i = 1;i<=(1<<n)-1;i++)cin >> a[i];
for(int i = (1<<n);i<=(1<<n+1)-1;i++)cin >> p[i];
for(int i = (1<<n);i<=(1<<n+1)-1;i++)pos[p[i]] = i-tot+1;
build(1,tot,1);
for(int i =0;i<=tot*2;i++)sort(val[i].begin(),val[i].end());
dfs(1,0);
for(auto x:ans)cout << x << " ";cout << '\n';
}
signed main(){
//freopen("maze5.in","r",stdin);
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t;cin >> t;
while(t--)solve();
return 0;
}/*
1
3 3
3 2 1 2 1 2 1
4 2 6 3 7 1 5 8
*/