题解/算法 {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] = 3
则MP1[2] = 3
,
2
∈
M
P
2
[
3
]
2 \in MP2[3]
2∈MP2[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 != x
则MP2(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;
}
};