CF1779F 题解

思路

说是树上背包吧,不太像;说不是吧,又有点像。不过最终还是觉得算吧,因为他的这类子树合并上去的思维像是树上背包,不过时间复杂度不是。

直接设 $f[u][i]$ 为 $u$ 这颗子树,要使异或和为 $i$ 的最小操作数,那么普通转移方程为:$$ f[u][i \oplus j]=\min(f[u][i \oplus j],f[u][i]+f[v][j]) $$ 当然还有,但是是要在子树大小为偶数且 $i$ 大于 $0$ 的情况下:$$ f[u][0]=\min(f[u][0],f[u][i]) $$

接下来是记录路径的问题 我们对于每个 $f[u][i]$ 都开个动态数组存 $u$ 的每个儿子转移过来那个位置的第二维(在我下面的代码里用 $j$ 表示),那么我们假设当前有一个 $u$ 和 $i$ ,那么我们将发现我们可以通过当前的 $i$ 异或上动态数组的当前儿子转移过来的那个 $j$ ,得到最后一个儿子合并上来前的新下标,由于 $i$ 不用了所以我们把他更新成那个新下标。最后记得特别注意要把第二类转移也要处理。

也许这时候你思考,那如果每个状态都开一个动态数组,空间会不会爆?那就继续往下看时空复杂度分析。

时空复杂度

时间复杂度 $O(n*w^2)$ ,其中 $w$ 为 $32$ (不难发现,异或和不会超过 $31$ ),其以树上背包的时间复杂度为主要。

空间 $O(n*w)$ , $w$ 同上。那个动态数组的话我们考虑对于每个节点,他只会在父节点的 $32$ 个动态数组里面出现一遍,所以还是 $O(n*w)$ 的。

代码

#include <bits/stdc++.h>
#define L(i, a, b) for(int i = a; i <= b; i++)
#define R(i, a, b) for(int i = a; i >= b; i--)
using namespace std;
const int N = 2e5 + 10;
int n, a[N], f[N][32], g[32], sz[N];
vector<int> ans, e[N], p[N][32];
void Dfs(int u, int pa){
    L(i, 0, 31) f[u][i] = 1e9;
    f[u][a[u]] = 0, sz[u] = 1;
    L(id, 0, (int)e[u].size() - 1){
        int v = e[u][id];
        Dfs(v, u), sz[u] += sz[v];
        L(i, 0, 31) g[i] = 1e9, p[u][i].push_back(-1);
        L(i, 0, 31){
            L(j, 0, 31){
                if(g[i ^ j] > f[u][i] + f[v][j]){
                    g[i ^ j] = f[u][i] + f[v][j];
                    p[u][i ^ j][id] = j;
                }
            }
        }
        L(i, 0, 31) f[u][i] = g[i];
    }
    if(!(sz[u] & 1))
        L(i, 1, 31)
            f[u][0] = min(f[u][0], f[u][i] + 1);
}
void Get(int u, int k){
    if(!e[u].size()) return;
    if(!k && !(sz[u] & 1)){
        L(i, 1, 31)
            if(f[u][i] + 1 == f[u][k]){
                ans.push_back(u);
                Get(u, i); return;
            }
    }
    R(i, (int)e[u].size() - 1, 0){
        int v = e[u][i];
        Get(v, p[u][k][i]);
        k ^= p[u][k][i];
    }
}
int main(){
    scanf("%d", &n);
    L(i, 1, n) scanf("%d", &a[i]);
    L(i, 2, n){
        int p; scanf("%d", &p);
        e[p].push_back(i);
    }
    Dfs(1, 0);
    if(f[1][0] == 1e9){
        puts("-1"); return 0;
    }
    printf("%d\n", f[1][0] + 1);
    Get(1, 0);
    reverse(ans.begin(), ans.end());
    ans.push_back(1);
    for(int u: ans) printf("%d ", u);
    return 0;
}
posted @ 2023-01-25 21:16  徐子洋  阅读(10)  评论(0)    收藏  举报  来源