1. 程式人生 > 其它 >LOJ3343. 「NOI2020」超現實樹

LOJ3343. 「NOI2020」超現實樹

定義一棵二叉樹“生長”得到的樹為:將其的所有葉子結點替換任意二叉樹,得到的樹。

給出一堆樹,現在問是否有限棵樹不能被這些樹中任意一個生長得到。

(題目表述為:幾乎所有樹都能被生長到)

\(\sum |T|\le 2*10^6\)


過了一年還不會做的神仙題。儘管是看了題解才會的,但還是儘量模擬一下正推的思路。

定義\(S\)能生長到\(T\)\(T\)\(S\)包含。嚴謹地表述一下,\(T\subseteq S\)當且僅當:\(T\)\(S\)的非葉子節點的交集的左右兒子狀態相同(是否有左兒子,是否有右兒子)。

題目相當於問是否能構造出一棵無限大的樹,滿足其不被任何一棵樹包含。

考慮看是否生長出一個基,這個基滿足:

  1. 幾乎包含了所有樹。
  2. 基中任何一棵樹都不能生成另一棵樹。

結合上面包含的定義(考慮左右兒子狀態),嘗試看看一個基包含了所有可能的左右兒子狀態會怎麼樣。

然後快進到結論:

欽定基中的每棵樹的高度為\(h\),有個深度\(1\)\(h-1\)的主鏈。主鏈上的每個點的兒子中除了那個在主鏈上的點之外另一個兒子為空或者為單點。(題解說:每個節點存在一個兒子大小不超過\(1\),稱這個東西為樹枝)

可以發現,所有高度為\(h\)的樹枝恰好組成了一個基。還有個很重要的是:非樹枝不能生成樹枝,也無法替代樹枝的作用。於是給出的樹中只需要丟掉非樹枝,判斷樹枝能否幾乎包含所有樹即可。

具體實現:遞迴,如果存在單點就返回1;否則分別遞迴左兒子(無右兒子),遞迴右兒子(無左兒子),遞迴左兒子(右兒子大小為1),遞迴右兒子(左兒子大小為1),要求都返回1。把每棵樹丟到它所屬的位置,注意可能存在樹同時屬於後兩者特判一下。


using namespace std;
#include <bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
int input() {
    char ch = getchar();

    while (ch < '0' || ch > '9')
        ch = getchar();

    int x = 0;

    do {
        x = x * 10 + ch - '0';
        ch = getchar();
    } while ('0' <= ch && ch <= '9');

    return x;
}
const int N = 2000005;
int m;
vector<pair<int, int>> _t[N];
pair<int, int> _q[N], q_[N];
int dfs(pair<int, int> q[], int k) {
    if (k == 0)
        return 0;

    int c[4] = {0, 0, 0, 0}, s[4]; //l0,l1,r0,r1
    int _11 = 0;

    for (int i = 1; i <= k; ++i) {
        vector<pair<int, int>> &t = _t[q[i].fi];
        int x = q[i].se;

        if (t[x].fi == 0 && t[x].se == 0)
            return 1;

        if (t[x].fi && t[x].se == 0)
            c[0]++;
        else if (t[x].se && t[x].fi == 0)
            c[2]++;
        else if (t[x].fi && t[x].se) {
            if (t[t[x].se].fi == 0 && t[t[x].se].se == 0 && t[t[x].fi].fi == 0 && t[t[x].fi].se == 0)
                _11 = 1;
            else if (t[t[x].se].fi == 0 && t[t[x].se].se == 0)
                c[1]++;
            else if (t[t[x].fi].fi == 0 && t[t[x].fi].se == 0)
                c[3]++;
        }
    }

    s[0] = c[0];

    for (int i = 1; i <= 3; ++i)
        s[i] = s[i - 1] + c[i];

    for (int i = 1; i <= k; ++i) {
        vector<pair<int, int>> &t = _t[q[i].fi];
        int x = q[i].se;

        if (t[x].fi && t[x].se == 0)
            q_[s[0]--] = mp(q[i].fi, t[x].fi);
        else if (t[x].se && t[x].fi == 0)
            q_[s[2]--] = mp(q[i].fi, t[x].se);
        else if (!_11 && t[x].fi && t[x].se) {
            if (t[t[x].se].fi == 0 && t[t[x].se].se == 0)
                q_[s[1]--] = mp(q[i].fi, t[x].fi);

            if (t[t[x].fi].fi == 0 && t[t[x].fi].se == 0)
                q_[s[3]--] = mp(q[i].fi, t[x].se);
        }
    }

    memcpy(q + 1, q_ + 1, sizeof(pair<int, int>)*k);
    return dfs(q, c[0]) && dfs(q + c[0] + c[1], c[2]) && (_11 || dfs(q + c[0], c[1]) &&
            dfs(q + c[0] + c[1] + c[2], c[3]));
}
int main() {
    //  freopen("in.txt","r",stdin);
    freopen("surreal.in", "r", stdin);
    freopen("surreal.out", "w", stdout);
    int T = input();

    while (T--) {
        m = input();

        for (int i = 1; i <= m; ++i) {
            int n = input();
            _t[i].resize(n + 1);

            for (int j = 1; j <= n; ++j) {
                int x = input(), y = input();
                //mp(input(),input())
                _t[i][j] = mp(x, y);
                //printf("%d %d\n",_t[i][j].fi,_t[i][j].se);
            }
        }

        for (int i = 1; i <= m; ++i)
            _q[i] = mp(i, 1);

        int ans = dfs(_q, m);

        if (ans)
            printf("Almost Complete\n");
        else
            printf("No\n");
    }

    return 0;
}