1. 程式人生 > >題解 洛谷P5018【對稱二叉樹】(noip2018T4)

題解 洛谷P5018【對稱二叉樹】(noip2018T4)

\(noip2018\) \(T4\)題解

  • 其實呢,我是覺得這題比\(T3\)水到不知道哪裡去了
  • 畢竟我比較菜,不大會\(dp\)

  • 好了開始講正事
  • 這題其實考察的其實就是選手對D(大)F(法)S(師)的掌握程度
  • 考完試有人說這題是馬拉車,嚇死我了
  • 首先,你把資料讀入之後,先用一個大法師把以每個節點為根的子樹的大小和權值都預處理出來,方便待會剪枝
  • 然後,你對以每個節點為根的子樹,都判斷一下以下條件(這時剛才處理的東西就有用了)

  • ① 左子樹和右子樹的節點數是否相等
  • ② 左子樹和右子樹的權值是否相等
  • ③ 以當前節點為根的子樹大小是不是超過答案

  • 第三個很重要,不加(洛谷資料)最後一個點會TLE

  • 有一個顯而易見的剪枝:因為答案至少是1,所以大小為1的子樹就不用check了,不然浪費常數

  • 然後就是暴力判了
  • 遞迴下去,建立兩個佇列,儲存當前處理到的左子樹上和右子樹上的節點,判左子樹當前節點的左兒子和右子樹當前節點的右兒子權值是否相等,右子樹當前節點的左兒子和左子樹當前節點的右兒子權值是否相等(注意對應)
  • 還有判下對應的節點有沒有一個是空的一個沒空的情況

  • 如果不相等就返回
  • 相等的話就扔進佇列(注意對應順序!)

  • 注意:上述處理一定要左右子樹一起做,不能先處理一邊,再處理另一邊,不然會WA

  • 到最後如果都可以的話就return true

  • 附考場程式碼
  • 不得不說,為了能過,我加了一堆卡常
  • 3e6的輸入規模應該還是要快讀的吧

# include <bits/stdc++.h>

# define R register

const int MaxN = 1000010;

struct node//節點
{
    int val;
    int l, r;
};

node a[MaxN];
int f[MaxN], val[MaxN], ind[MaxN];//f[i]表示以i為根的子樹大小,val表示以i為根的子樹權值和,ind沒啥用

inline void read(int &x)//快讀
{
    x = 0;
    bool op = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
            op = 0;
        ch = getchar();
    }
    while(ch <= '9' && ch >= '0')
        x = (x << 1) + (x << 3) + (ch - '0'), ch = getchar();
    if(!op)
        x = -x;
}

void dfs(int root)
{
    if(root == -1)
        return;
    if(a[root].l == -1 && a[root].r == -1)
        f[root] = 1, val[root] = a[root].val;
    else
    {
        dfs(a[root].l);
        dfs(a[root].r);
        f[root] = f[a[root].l] + f[a[root].r] + 1;
        val[root] = val[a[root].l] + val[a[root].r] + a[root].val;
    }

}

inline int check(int x)
{
    std::queue<int> l, r;
    l.push(x), r.push(x);
    while(!l.empty() || !r.empty())
    {
        if(l.empty() || r.empty())
            return false;//一邊空了,一邊沒空
        R int lx = l.front(), rx = r.front();
        l.pop(), r.pop();
        if(a[lx].val != a[rx].val)
            return false;
        R int lson[3], rson[3];
        lson[1] = a[lx].l, lson[2] = a[lx].r;//左子樹當前節點的左兒子,左子樹當前節點的右兒子
        rson[1] = a[rx].l, rson[2] = a[rx].r;//右子樹當前節點的左兒子,右子樹當前節點的右兒子
        if((lson[1] == -1 && rson[2] != -1) || (lson[1] != -1 && rson[2] == -1))
            return false;//一邊空了,一邊沒空
        if((lson[2] == -1 && rson[1] != -1) || (lson[2] != -1 && rson[1] == -1))
            return false;//一邊空了,一邊沒空
        if(lson[1] != -1)
            l.push(lson[1]);
        if(lson[2] != -1)
            l.push(lson[2]);
        if(rson[2] != -1)
            r.push(rson[2]);
        if(rson[1] != -1)
            r.push(rson[1]);
        //推進佇列
    }
    return true;
}

int main()
{
//  freopen("tree.in", "r", stdin);
//  freopen("tree.out", "w", stdout);
    R int n;
    scanf("%d", &n);
    for(R unsigned i = 1; i <= n; ++i)
        read(a[i].val);
    for(R unsigned i = 1; i <= n; ++i)
        read(a[i].l), read(a[i].r), ++ind[a[i].l], ++ind[a[i].r];//處理入度
    R unsigned root;
    for(R unsigned i = 1; i <= n; ++i)
    {
        if(!ind[i])
        {
            root = i;
            break;
        }
    }//找樹根
    dfs(root);//預處理
    int ans = 1;
    for(R unsigned i = 1; i <= n; ++i)//列舉子樹
    {
        if(f[a[i].l] != f[a[i].r])
            continue;//剪枝1
        if(val[a[i].l] != val[a[i].r])
            continue;//剪枝2
        if(f[i] < ans || f[i] == 1)
            continue;//剪枝3
        if(check(i))
            ans = f[i];//更新答案
    }
    printf("%d", ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}