1. 程式人生 > 實用技巧 >題解 CF741D 【Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths】

題解 CF741D 【Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths】

\[\text{CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths} \]

\(\quad\)題目連結:CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths(洛谷的連結)

\(\quad\)這其實算一道 Dsu 的壓軸題,據說是樹上啟發式合併演算法的創始者出的題。

\(\quad\)這題確實是有些難度的,總之我一開始連題解都沒有看懂。

\(\quad\)首先考慮迴文的問題,其他題解其實講的很清楚了,只要22個字母中最多有一個字母數量為奇數即可,也可都為偶數,所以一共23種情況,但考慮所有情況(只分奇偶)有 \(2^{22}\)

中情況,可以用一個二進位制數表示,\(cnt_i\) 表示二進位制數為 \(i\) 的結點的最大深度,二進位制數指的是從這個結點到根節點的最短路徑的序列,\(num_x\) 表示結點 \(x\) 到根節點的最短路徑的序列,請仔細理解這句話,否則之後的程式碼可能會看不懂。

\(\quad\)然後我們對於兩個修改函式都講一遍。

\(\quad\)第一個修改函式,就是判斷是否有有符合條件的,如對於節點 \(x\) 來說,和TA到根節點的序列為 \(num_x\)\(cnt_{num_x}\) 表示之前出現的另一條大小為 \(num_x\) 序列,這樣這兩條路徑合併後字母數就都是偶數,之後的迴圈列舉的是有一個字母不同的情況,這兩種情況都是符合條件的。

il void add1(int x)
{
  ans[now]=max(ans[now],dep[x]+cnt[num[x]]);
  for(re i=0;i<=21;i++)ans[now]=max(ans[now],dep[x]+cnt[(1<<i)^num[x]]);
}

\(\quad\)對於第二個修改函式,就是把這個結點 \(x\) 的資訊載入 \(cnt\) 陣列,並且為了最後的序列最長,要儘可能選深度大的,顯然深度大的答案更優。

il void add2(int x)
{cnt[num[x]]=max(cnt[num[x]],dep[x]);}

\(\quad\)

注意要先做修改操作 \(1\),再做修改操作 \(2\),也就是說先統計這個點的答案(或一棵子樹),再載入這個點的資料(或一棵子樹),否則答案會把自己也記進去,可以仔細思考一下這個點。

\(\quad\)接下來我們思考一個問題,因為我們是一棵子樹一棵子樹為單位修改的,如果這個最優答案在子樹中會怎麼樣?可以發現這樣的答案在子樹中一定被統計過了,當這條路徑的兩個端點的LCA被詢問時就以及被記錄了,所以還要跑一遍所有子樹,用子樹的答案來更新當前結點。

\(\quad\)另外我們還要注意節點 \(i\) 的答案的計算公式為

\[ans_i=\max (dep_x+dep_y-2\times dep_i) \]

\(\quad\)這其實就是 \(x\)\(y\) 兩點之間的距離公式( \(x\),\(y\) 為最短路徑的兩個端點),另外可以發現最優情況下結點 \(i\) 為結點 \(x\) 和結點 \(y\) 的LCA,因為結點 \(x\) 和結點 \(y\) 的在以 \(i\) 為根節點的子樹,若不是的話,那麼答案就會算多,但這顯然是錯誤的答案,所以我們是一棵子樹一棵子樹為單位修改的,這也算回答了上面的問題。

\(\quad\)最後來看看完整程式碼吧!

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;
#define re register int
#define il inline
#define next nee
#define inf 1e9+5
il int read()
{
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)&&ch!='-')ch=getchar();
  if(ch=='-')f=-1,ch=getchar();
  while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
  return x*f;
}
il void print(int x)
{
  if(x<0)putchar('-'),x=-x;
  if(x/10)print(x/10);
  putchar(x%10+'0');
}
const int N=5e5+5;
int n,m,next[N],go[N],head[N],tot,seg[N],son[N],father[N],now;
int size[N],rev[N],ans[N],s[N],dep[N],num[N],cnt[1<<23];
il int Max(int x,int y){return x>y?x:y;}
il void Add(int x,int y,int z)
{
  next[++tot]=head[x];
  head[x]=tot;go[tot]=y;s[tot]=z;
}
il void add1(int x)//修改操作1
{
  ans[now]=max(ans[now],dep[x]+cnt[num[x]]);
  for(re i=0;i<=21;i++)ans[now]=max(ans[now],dep[x]+cnt[(1<<i)^num[x]]);
}
il void add2(int x)//修改操作2
{cnt[num[x]]=max(cnt[num[x]],dep[x]);}
il void clear(int x)//清空操作
{
  for(re i=seg[x];i<=seg[x]+size[x]-1;i++)
    cnt[num[rev[i]]]=-inf;
}
il void dfs1(int x)
{
  dep[x]=dep[father[x]]+1;size[x]=1;seg[x]=++seg[0];rev[seg[x]]=x;
  for(re i=head[x],y;i,y=go[i];i=next[i])
    {
      num[y]=num[x]^(1<<s[i]);dfs1(y);
      size[x]+=size[y];
      if(size[y]>size[son[x]])son[x]=y;
    }
}
il void dfs2(int x,int flag)
{
  for(re i=head[x],y;i,y=go[i];i=next[i])
    {
      if(y==son[x])continue;
      dfs2(y,0);
    }if(son[x])dfs2(son[x],1);now=x;
  for(re i=head[x],y;i,y=go[i];i=next[i])
    {
      if(y==son[x])continue;
      for(re i=seg[y];i<=seg[y]+size[y]-1;i++)add1(rev[i]);
      for(re i=seg[y];i<=seg[y]+size[y]-1;i++)add2(rev[i]);
    }add1(x),add2(x);//記得要修改x結點
  ans[x]-=(dep[x]<<1);//減去本身的深度
  for(re i=head[x],y;i,y=go[i];i=next[i])ans[x]=max(ans[x],ans[y]);
  if(!flag)clear(x);
}
signed main()
{
  n=read();char ch;
  for(re i=0;i<(1<<22);i++)cnt[i]=-inf;//一定要初始化為負值
  for(re i=2,x;i<=n;i++){x=read();father[i]=x;scanf("%c",&ch);Add(x,i,ch-'a');}
  dfs1(1);dfs2(1,1);
  for(re i=1;i<=n;i++)print(Max(ans[i],0)),putchar(' ');//可能會輸出負數
  return 0;
}