题解/算法 {2791. 树中可以形成回文的路径数}

题解/算法 {2791. 树中可以形成回文的路径数}

LINK: https://leetcode.cn/problems/count-paths-that-can-form-a-palindrome-in-a-tree/;

根据树上路径LINK: (https://editor.csdn.net/md/?articleId=128695608)-(@LOC_0), 结合字符串的回文值: LINK: (https://editor.csdn.net/md/?articleId=130465093)-(@LOC_0);

可以想到一个暴力的做法: 对每个节点X 设置一个map<int,int> Cont 对于所有X的子节点S 令X-S路径的回文值为V 则执行Cont[ V] ++, 即我们将所有路径分成了N类 根据路径上的最近公共祖先分成了N类, 对于最近公共祖先为X的答案路径个数 可通过其Cont[]算出;
即对于路径a---x---b(x为最近公共祖先), 将他分成2部分 令A为a---x的回文值 令B为x---b的回文值, 根据回文值的性质 A^B就等于原字符串的回文值; 因此遍历所有回文值X,Y 如果X^Y 是合法的回文值(即{0/1/2/4/8/...})Cont[X] * Cont[Y]就是答案;
但这会超时, 因为任意点 他有N个子节点, 因此一共是N^2个;

接着上面的讲 你通过A^B来获得这条路径的回文值, 而根据异或的性质 A^B ^x^x = A^B , 因此你可以让(A^x)替代A 来表示a---x这个路径的回文值, 这个思路非常跳跃性, 因为我们并不在乎a---x的回文值 到底是多少 我们在乎的是 要计算出a---x---b这个路径的回文值, 这是本题的难点;
因此, 我们令V[x]表示 x点到根节点路径的回文值, 则a---x---b的回文值 就等于V[a] ^ V[b] (虽然说 V[a] != 回文值(a---x));

MP1: int->int, MP1(x)=y 表示V[x] = y;
MP2: int->set<int>, MP2(x)=y 表示 所有满足V[?] = x的点的集合 为y;
. 比如V[2] = 3MP1[2] = 3, 2 ∈ M P 2 [ 3 ] 2 \in MP2[3] 2MP2[3];
有个性质非常重要: 如果a != b, 则MP2(a), MP2(b)两个集合里 不会存在相同元素, 因为任意点 他的V[]值 是唯一固定的;

进行一次DFS 从树根开始 构造出Cont[]数组 (令x点到树根的回文值为c 则执行Cont[c] ++); 准确说 不是数组 是个map; 因为他是个长度为26的二进制数 (注意不是26进制数);

{0,1,2,4,8,16,...}为合法回文值, 则对于x ^ y为合法回文值, 假如MP2(x) = a,b,c 如果y != xMP2(y) = d,e, 则Cont[x] * Cont[y] 就表示这些路径(a,d) (a,e) (b,d) (b,e) ...; (具体做法为 遍历合法回文值tar 然后遍历整个x : Cont 执行sum += Cont[x] * Cont[ tar ^x], 最后还有执行sum /= 2 (因此你还计算了(d,a) (d,b) (d,c) ...;
时间是26 * 1e5;

但是有一点是错的, 就是tar = 0的情况, 注意上面说的前提是*y != x* 注意这个前提 只有满足他 才会有{a,b,c} {d,e} (当tar != 0时, 此时一定有x != y, 因为只有x^x = 0), 但是当tar = 0时 此时x = y, 即{a,b,c} {a,b,c} 上面的算法就出错了;
比如举个例子, tar = 0时 有{a,b,c} {a,b,c} {d,e} {d,e}, 那么你(3*3 + 2*2) / 2是错的 (而且他还不是整除), 正确做法是: 3*3里 把(aa, bb, cc)给去掉 然后再把(ab) (ba)进行除2操作 即(3*3 - 3)/2;

因此, tar != 0时, 我们是先把所有的Cont[x] * Cont[y]累加起来后 再除2;
tar = 0时 是对单个(Cont[x] * Cont[x] - Cont[x]) / 2的累加;

class Solution {
public:
    Graph< int> G = Graph<int>(1e5,1e5,1e5);
    unordered_map< int, int> Cont;
    void Dfs( int _cur, int _st){
        Cont[ _st] ++;

        for( int nex, edge = G.Head[ _cur]; ~edge; edge = G.Next[ edge]){
            nex = G.Vertex[ edge];
            Dfs( nex, _st ^ (1 << G.Weight[ edge]));
        }
    }
    long long countPalindromePaths(vector<int>& P, string S) {
        { static bool __is_first = true;
            if( __is_first){ __is_first = false;
            }
        }
        {
            G.Initialize( P.size());
            Cont.clear();
        }

        int n = P.size();
        int root;
        for( int i = 0; i < n; ++i){
            if( P[i] == -1){ root = i; continue;}

            G.Add_edge( P[i], i, S[i] - 'a');
        }

        Dfs( root, 0);

        long long ANS = 0;
        for( auto & item : Cont){
            if( Cont.find( item.first) != Cont.end()){
                ANS += ( (long long)item.second * item.second - item.second) / 2;
            }
        }
        for( int bit = 0; bit < 26; ++bit){
            int tar = (1 << bit);

            long long sum = 0;
            for( auto & item : Cont){
                auto t = tar ^ item.first;
                if( Cont.find( t) != Cont.end()){
                    sum += ((long long)item.second * Cont[ t]);
                }
            }
            sum /= 2;
            ANS += sum;
        }

        return ANS;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值