CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
大概是dsu on tree的最难题了吧...还是存在有些地方没有想通。暂时先存下来。
讲的比较详细的思路:这里
Soution:
注意到字母最大是v,即最多有22种字符,看到这个数据范围就想到利用状压。那么在这题怎么用上状压呢?想到一个性质:一个简单路径可以通过打乱变成回文串,当且仅当在这个路径上出现的次数为奇数的字符个数不超过一个。
我们状压记录每个字符出现的奇偶性,偶数为0,奇数为1,那么对于以x xx为根的子树中,记s t a p sta_pstap为p点到x的路径状态,s t a q sta_qstaq为q点到x的路径状态,那么显然这个总路径的状态就是s t a p sta_pstap ^ s t a q sta_qstaq,暂记为r e t retret,显然路径合法的条件是:r e t retret中1的个数 ≤ 1 \leq 1≤1。
知道了如何记录,先考虑暴力求解。
s u b t a s k 1 : subtask_1:subtask1:从根节点x开始搜索,记录所有路径的状态,如何找到这个状态是否和其他链异或起来有合法解?其实也简单,考虑到异或性质的特殊性,即x ^ y = z,则x ^ z = y.那么把当前的路径状态和所有的合法状态(显然只有23种)异或一下,结果暂记为n o w nownow,再查找是否有n o w nownow这个状态就行了。
s u b t a s k 2 : subtask_2:subtask2:如何得出答案?先求出所有节点的深度,记为d e p i dep_idepi,那么对于上述的合法状态,答案就是d e p p + d e p q − 2 ∗ d e p x dep_p + dep_q - 2 * dep_xdepp+depq−2∗depx,可以看出d e p p dep_pdepp和d e p q dep_qdepq越大越好,所以对于有相同状态的路径,只需要记录深度最深的那个就行了,这样复杂度也有保证。
上述的暴力求解因为每次要清空状态数组重新记录,复杂度O ( 23 ∗ n 2 ) O(23 * n^2)O(23∗n2),无法通过,想到树的特殊性,题目又是静态的没有修改,加入树上启发式合并,复杂度降为O ( 23 ∗ n ∗ l o g 2 n ) O(23 * n * log_2n)O(23∗n∗log2n),就可以通过啦。
代码得思考一下怎么写。
讲真这个内部的路径处理问题感觉我想的还不是很通透,就不误人子弟了。待我复习
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<unordered_map>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=5e5+1000;
typedef int LL;
LL siz[maxn],son[maxn],dep[maxn],flag=0;
LL xor_sum[maxn];
LL cnt[1<<22];
LL ans[maxn];
vector< pair<LL,LL> >g[maxn];
void predfs(LL u,LL fa){
siz[u]=1;dep[u]=dep[fa]+1;
for(LL i=0;i<g[u].size();i++){
LL v=g[u][i].first;
LL k=g[u][i].second;
if(v==fa) continue;
xor_sum[v]=xor_sum[u]^k;///前缀异或,类似前缀和
predfs(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]]){
son[u]=v;
}
}
}
void cal(LL u,LL fa,LL lca,LL val){
if(cnt[xor_sum[u]]) ans[lca]=max(ans[lca],cnt[xor_sum[u]]+dep[u]-2*dep[lca]);///枚举的根节点作为lca时候的贡献
for(LL i=0;i<=21;i++) if(cnt[xor_sum[u]^(1<<i)]) ans[lca]=max(ans[lca],cnt[xor_sum[u]^(1<<i)]+dep[u]-2*dep[lca]);
/// cnt[xor_sum[u]]=max(cnt[xor_sum[u]],dep[u]);
for(LL i=0;i<g[u].size();i++){
LL v=g[u][i].first;
if(v==fa||v==flag) continue;
cal(v,u,lca,val);
}
}
void add(LL u,LL fa,LL val){
cnt[xor_sum[u]]=max(cnt[xor_sum[u]],dep[u]);
for(LL i=0;i<g[u].size();i++){
LL v=g[u][i].first;
if(v==fa||v==flag) continue;
add(v,u,val);
}
}
void del(LL u,LL fa,LL val){
cnt[xor_sum[u]]=0;
for(LL i=0;i<g[u].size();i++){
LL v=g[u][i].first;
if(v==fa||v==flag) continue;
del(v,u,val);
}
}
void dfs(LL u,LL fa,bool keep)
{
for(LL i=0;i<g[u].size();i++){
LL v=g[u][i].first;
if(v==fa||v==son[u]) continue;
dfs(v,u,0);
ans[u]=max(ans[u],ans[v]);///来自不经过u点的轻儿子的最大值转移
}
if(son[u]){
dfs(son[u],u,1);
flag=son[u];
ans[u]=max(ans[u],ans[son[u]]);///来自不经过u点的重儿子的最大值转移
}
cnt[xor_sum[u]]=max(cnt[xor_sum[u]],dep[u]);///维护相同状态的路径,只需要记录深度最深的那个
ans[u]=max(ans[u],cnt[xor_sum[u]]-dep[u]);///维护一端连着u,一端在u的子树的内部的链子转移
for(LL i=0;i<=21;i++) if(cnt[xor_sum[u]^(1<<i)]) ans[u]=max(ans[u],cnt[xor_sum[u]^(1<<i)]-dep[u]);
for(LL i=0;i<g[u].size();i++){
LL v=g[u][i].first;
if(v==fa||v==flag) continue;
cal(v,u,u,1);
add(v,u,1);
}
/// cal(u,fa,u,1);
/// add(u,fa,1);
///先计算答案,再更新cnt[]
flag=0;
if(keep==0){
del(u,fa,-1);
}
}
int main(void)
{
///cin.tie(0);std::ios::sync_with_stdio(false);
///题目以1为根
LL n;scanf("%d",&n);
for(LL i=2;i<=n;i++){
LL x;char str;scanf("%d %c",&x,&str);
g[x].push_back({i,(1<<(str-'a'))});
g[i].push_back({x,(1<<(str-'a'))});
}
predfs(1,0);
dfs(1,0,0);
for(LL i=1;i<=n;i++){
printf("%d ",ans[i]);
}
printf("\n");
return 0;
}