树上启动式合并问题 ---- D. Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths [状态压缩+树上启发式合并]

本文介绍了如何解决一道关于树上寻找最长Dokhtar-kosh路径的问题,即路径上的字符重新排序后能形成回文串。通过状态压缩和树上启发式合并的方法,利用异或运算维护路径的回文性质,并在递归过程中更新子树的最大回文路径长度。解题过程中涉及到预处理路径异或和、暴力统计、动态规划以及树的深度优先搜索等技巧。
摘要由CSDN通过智能技术生成

题目链接


题目大意:

一棵根为1 的树,每条边上有一个字符( a − v a−v av共22种)。 一条简单路径被称为Dokhtar−kosh当且仅当路径上的字符经过重新排序后可以变成一个回文串。 求每个子树中最长的Dokhtar−kosh路径的长度。


解题思路:

首先我们观察一下,字母只有22种,我们可以用状态压缩去表示字符串的表示方式?
1.什么状态是合法的我们知道回文串奇数个字符只能个数只能是一种,或者全部都是偶数个的字符串。
2.那么我们就可以用异或去维护这个信息,我知道一个数异或了偶数次之后就是0,奇数次就是1,那么就异或值是 2 i 2^i 2i
3.对于求出每个子树的答案,这很明显是树上启发式合并。

我们看看要维护那些信息?
3.1 首先我们先预处理出从每个点从根节点路径上整个的异或和
3.2 首先我们知道对于每个点我们先暴力统计答案,更新答案,然后再递归下去求重儿子树启的常规操作
3.3 对于轻儿子我们可以暴力合并答案到重子树,
首先我们分类讨论求:
1)如果是异或和为0,那么重子树里面肯定有轻子树一样的路径这样异或才为0;
2)如果是异或和为 2 i 2^i 2i 那么我们可以暴力循环0到22给它异或上 2 i 2^i 2i

因 为 这 是 边 权 , 所 以 我 们 要 注 意 我 们 上 面 统 计 的 都 是 两 颗 子 树 之 间 的 路 径 因为这是边权,所以我们要注意我们上面统计的都是两颗子树之间的路径
但 是 我 们 知 道 这 个 路 径 可 以 是 一 条 直 链 , 那 么 我 们 还 要 统 计 一 下 是 直 链 的 情 况 但是我们知道这个路径可以是一条直链,那么我们还要统计一下是直链的情况


#include <bits/stdc++.h>

using namespace std;
const int maxn = 4e6 + 10;
struct node {
    int nxt, to, val;
}edge[maxn];
int head[maxn], cnt;
inline void add(int from, int to, int val) {
    edge[cnt] = {head[from],to,val-'a'};
    head[from] = cnt ++;
}
int n;
int siz[maxn], son[maxn], depth[maxn];
int state[maxn << 1];
void find_son(int u, int fa) {
    siz[u] = 1;
    depth[u] = depth[fa] + 1;
    for(int i = head[u]; ~i; i = edge[i].nxt) {
        int v = edge[i].to;
        if(v == fa) continue;
        state[v] = (state[u] ^ (1ll << edge[i].val));//统计路径异或和
        find_son(v,u);
        siz[u] = siz[u] + siz[v];
        if(son[u] == 0 || siz[son[u]] < siz[v]) 
           son[u] = v;//找重子树
    }
}

int root_son;//
int ans[maxn];
int state_has[maxn << 2];//存的是每个异或和最大深度是多少
int updep, maxx;
void dsu(int u, int fa) {
    if(state_has[state[u]]) //如果存在一样的异或和路径才转移
       maxx = max(maxx,state_has[state[u]] + depth[u] - updep);//因为我们只考虑在这个子树内部肯定要减掉上的深度

    for(int i = 0; i <= 22; ++ i)//暴力枚举奇数
      if(state_has[state[u] ^ (1ll << i)])
         maxx = max(maxx, state_has[state[u] ^ (1ll << i)] + depth[u] - updep);  

    
    for(int i = head[u]; ~i; i = edge[i].nxt) {
        int v = edge[i].to;
        if(v == fa || v == root_son) continue;
        dsu(v,u);
    }

}

void update(int u, int fa, int op) {
    if(op == 1) {
        state_has[state[u]] = max(state_has[state[u]],depth[u]);
    } 
    else 
    state_has[state[u]] = 0;

    for(int i = head[u]; ~i; i = edge[i].nxt) {
        int v = edge[i].to;
        if(v == fa || v == root_son) continue;
        update(v,u,op);
    }

}

void dfs(int u, int fa, int keep) {

    for(int i = head[u]; ~i; i = edge[i].nxt) {
        int v = edge[i].to;
        if(v == fa || v == son[u]) continue;
        dfs(v,u,0);
        ans[u] = max(ans[v],ans[u]);
    }
    
    if(son[u]) {
        dfs(son[u],u,1);
        root_son = son[u];
        ans[u] = max(ans[root_son],ans[u]);
    }

    updep = depth[u] * 2;

    for(int i = head[u]; ~i; i = edge[i].nxt) {
        int v = edge[i].to;
        if(v == fa || v == root_son) continue;
        dsu(v,u);
        update(v,u,1);
    }
    if(state_has[state[u]]) maxx = max(maxx,state_has[state[u]] - depth[u]);
    for(int i = 0; i <= 22; ++ i)
     if(state_has[state[u] ^ (1ll << i)])
       maxx = max(maxx,state_has[state[u] ^ (1ll << i)] - depth[u]);
    
    state_has[state[u]] = max(state_has[state[u]],depth[u]);
    ans[u] = max(maxx,ans[u]);
    
    root_son = 0;

    if(!keep) {
        update(u,fa,-1);
        maxx = updep = 0;
    }

}

int main() {
    memset(head,-1,sizeof(head));
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    //..............................
    cin >> n;
    for(int i = 2; i <= n; ++ i) {
        int a; char b;
        cin >> a >> b;
        add(a,i,(int)b);
        add(i,a,(int)b);
    }
    find_son(1,0);
    dfs(1,0,0);
    for(int i = 1; i <= n; ++ i) 
       cout << ans[i] << " ";
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值