1 条题解

  • 0
    @ 2025-11-25 9:12:42

    凸多边形三角划分最大切割数题解

    一、问题核心分析

    题目要求在凸多边形的三角划分中,找到最大切割次数,使得切割后没有任何颜色的三角形被分割到两个不同部分。关键在于理解切割的本质约束:同一颜色的所有三角形必须属于同一个连通区域,切割线不能穿过任何颜色的“连通块”。

    三角划分的凸多边形可抽象为一个树结构(称为“对偶树”):

    • 每个节点代表一个三角形;
    • 两个节点之间有边,当且仅当对应的两个三角形共享一条内部对角线(非多边形边界)。

    此时问题转化为:在对偶树上找到最多的边,使得删除这些边后,每个颜色对应的节点集合仍保持连通。最大切割数即为“对偶树中可删除的边数”,等于“对偶树总边数 - 必须保留的边数”。

    二、算法原理

    1. 构建对偶树

      • 三角划分中每个三角形有三条边,其中非多边形边界的边是内部对角线。
      • 用哈希表记录每条对角线对应的节点(三角形),对于每个三角形,将其三条对角线对应的节点(若存在)相连,形成对偶树。
    2. 颜色连通性约束

      • 对于每种颜色,其对应的所有三角形(对偶树节点)必须连通。在树结构中,保持节点集合连通的最小边集是该节点集合的“生成树”,需保留的边数为“节点数 - 1”。
      • 利用树的差分技巧(结合LCA)计算每种颜色对边的“必须保留”标记:对颜色c的节点按DFS序排序,相邻节点及其LCA构成的路径上的边需保留,通过差分数组标记。
    3. 计算最大切割数

      • 遍历对偶树的所有边,统计未被任何颜色标记为“必须保留”的边数,即为最大切割数。

    三、完整代码

    #include <bits/stdc++.h>
    using namespace std;
    #define res int
    #define LL long long
    #define Inf 0x3f3f3f3f
    #define sup 0x7fffffff
    #define inf 0x3f3f3f3f
    #define INF 2000000000000000000
    #define eps 1e-10
    #define RG
    #define db double
    #define pc(x) __builtin_popcount(x)
    #define ctz(x) __builtin_ctz(x)
    typedef pair<int, int> Pair;
    #define poly vector<int>
    #define mp make_pair
    #define fi first
    #define se second
    #define pi acos(-1.0)
    #define pb push_back
    #define ull unsigned LL
    #define uint unsigned int
    #define lowbit(x) ((x)&-(x))
    #define gc getchar
    #define ld long db
    template <typename T> inline void Read(T &x) {
        res c = gc();
        bool f = false;
        for (x = 0; !isdigit(c); c = gc())
            if (c == '-')
                f = true;
        for (; isdigit(c); c = gc())
            x = x * 10 + c - '0';
        if (f)
            x = -x;
    }
    inline int read() {
        res s = 0, ch = gc(), w = 1;
        while (ch < '0' || ch > '9') {
            if (ch == '-')
                w = -1;
            else if (ch == EOF)
                break;
            ch = gc();
        }
        while (ch >= '0' && ch <= '9')
            s = s * 10 + ch - '0', ch = gc();
        return s * w;
    }
    inline LL Read() {
        RG LL s = 0;
        res ch = gc(), w = 1;
        while (ch < '0' || ch > '9') {
            if (ch == '-')
                w = -1;
            else if (ch == EOF)
                break;
            ch = gc();
        }
        while (ch >= '0' && ch <= '9')
            s = s * 10 + ch - '0', ch = gc();
        return s * w;
    }
    const int kcz = 998244353;
    const int G = 3, GI = 332748118;
    #define add(x,y) ((x)+=(y),(x)>=kcz?(x)-=kcz:1)
    #define Add(x,y) ((x)+(y)>=kcz?(x)+(y)-kcz:(x)+(y))
    #define mul(x,y) (int)((LL)(x)*(y)%kcz)
    #define Mul(x,y,d) (int)((ull)(x)*(y)/(d)%kcz)
    inline int qpow(res x, res y = kcz - 2) {
        res ret = 1;
        while (y) {
            if (y & 1)
                ret = mul(ret, x);
            x = mul(x, x), y >>= 1;
        }
        return ret;
    }
    inline int qpow(res x, res y, const res &ljc) {
        res ret = 1;
        while (y) {
            if (y & 1)
                ret = (int)(1ll * ret * x % ljc);
            x = (int)(1ll * x * x % ljc), y >>= 1;
        }
        return ret;
    }
    const int N = 1e5 + 10;
    namespace MAIN {
    int n;
    map<Pair, int> M;
    vector<int> G[N];
    int F[N][21], dfn[N], idx, dep[N];
    void dfs(res x, res fax) {
        F[x][0] = fax, dfn[x] = ++idx;
        for (res i = 1; i <= 20; i++)
            F[x][i] = F[F[x][i - 1]][i - 1];
        for (auto tox : G[x]) {
            if (tox == fax)
                continue;
            dep[tox] = dep[x] + 1, dfs(tox, x);
        }
    }
    inline int get_lca(res x, res y) {
        if (dep[x] < dep[y])
            swap(x, y);
        res d = dep[x] - dep[y];
        for (res i = 20; ~i; i--)
            if (d & (1 << i))
                x = F[x][i];
        if (x == y)
            return x;
        for (res i = 20; ~i; i--)
            if (F[x][i] != F[y][i])
                x = F[x][i], y = F[y][i];
        return F[x][0];
    }
    vector<int> A[N];
    LL f[N];
    void dfs_f(res x, res fax) {
        for (auto tox : G[x]) {
            if (tox == fax)
                continue;
            dfs_f(tox, x), f[x] += f[tox];
        }
    }
    inline void MAIN() {
        n = read();
        for (res i = 1; i <= n - 2; i++) {
            res a = read(), b = read(), c = read(), d = read();
            if (a > b)
                swap(a, b);
            if (b > c)
                swap(b, c);
            if (a > b)
                swap(a, b);
            if (M.count(mp(a, b)))
                G[M[mp(a, b)]].pb(i), G[i].pb(M[mp(a, b)]);
            else
                M[mp(a, b)] = i;
            if (M.count(mp(b, c)))
                G[M[mp(b, c)]].pb(i), G[i].pb(M[mp(b, c)]);
            else
                M[mp(b, c)] = i;
            if (M.count(mp(a, c)))
                G[M[mp(a, c)]].pb(i), G[i].pb(M[mp(a, c)]);
            else
                M[mp(a, c)] = i;
            A[d].pb(i);
        }
        dfs(1, 0);
        for (res i = 1; i <= n - 2; i++) {
            sort(A[i].begin(), A[i].end(), [&](const res & x, const res & y) {
                return dfn[x] < dfn[y];
            });
            res pre = 0;
            for (auto x : A[i]) {
                if (pre) {
                    res lca = get_lca(pre, x);
                    f[x]++, f[pre]++, f[lca] -= 2;
                }
                pre = x;
            }
        }
        dfs_f(1, 0);
        res ans = 0;
        for (res i = 2; i <= n - 2; i++)
            if (!f[i])
                ans++;
        printf("%d\n", ans);
    }
    }
    int main() {
        res Case = 1;
        for (res T = 1; T <= Case; T++)
            MAIN::MAIN();
        return 0;
    }
    

    四、代码关键步骤解析

    1. 对偶树构建

      • 对于每个三角形的三个顶点,先排序生成有序对(确保对角线表示唯一),用哈希表M记录每条对角线对应的三角形节点。
      • 若两条三角形共享某条对角线,则在对偶树中对应的节点之间添加边。
    2. LCA预处理与DFS序

      • 对於对偶树,进行DFS遍历,记录每个节点的DFS序dfn、深度dep,并预处理LCA的倍增数组F,用于快速计算任意两个节点的最近公共祖先。
    3. 颜色连通性标记

      • 对每种颜色,将其对应的节点按DFS序排序,相邻节点及其LCA构成的路径是保持该颜色连通的必要边。
      • 利用差分数组f标记路径:f[x]++f[pre]++f[lca] -= 2,后续通过子树求和得到每条边被标记的次数(次数>0表示必须保留)。
    4. 计算最大切割数

      • 对於对偶树进行子树求和,得到每条边的保留标记次数。
      • 对偶树总边数为(n-2)-1 = n-3(树的边数=节点数-1,对偶树节点数为n-2),未被标记(f[i]==0)的边可切割,统计其数量即为答案。

    五、复杂度分析

    • 时间复杂度:O(nlogn)O(n \log n)。其中对偶树构建为O(n)O(n),LCA预处理为O(nlogn)O(n \log n),每种颜色的节点排序与路径标记为O(nlogn)O(n \log n)(总节点数为n2n-2),子树求和为O(n)O(n)
    • 空间复杂度:O(n)O(n)。用于存储对偶树、哈希表、LCA数组、差分数组等,可满足n1e5n \leq 1e5的约束。
    • 1

    信息

    ID
    5566
    时间
    2000ms
    内存
    512MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者