1 条题解
-
0
凸多边形三角划分最大切割数题解
一、问题核心分析
题目要求在凸多边形的三角划分中,找到最大切割次数,使得切割后没有任何颜色的三角形被分割到两个不同部分。关键在于理解切割的本质约束:同一颜色的所有三角形必须属于同一个连通区域,切割线不能穿过任何颜色的“连通块”。
三角划分的凸多边形可抽象为一个树结构(称为“对偶树”):
- 每个节点代表一个三角形;
- 两个节点之间有边,当且仅当对应的两个三角形共享一条内部对角线(非多边形边界)。
此时问题转化为:在对偶树上找到最多的边,使得删除这些边后,每个颜色对应的节点集合仍保持连通。最大切割数即为“对偶树中可删除的边数”,等于“对偶树总边数 - 必须保留的边数”。
二、算法原理
-
构建对偶树:
- 三角划分中每个三角形有三条边,其中非多边形边界的边是内部对角线。
- 用哈希表记录每条对角线对应的节点(三角形),对于每个三角形,将其三条对角线对应的节点(若存在)相连,形成对偶树。
-
颜色连通性约束:
- 对于每种颜色,其对应的所有三角形(对偶树节点)必须连通。在树结构中,保持节点集合连通的最小边集是该节点集合的“生成树”,需保留的边数为“节点数 - 1”。
- 利用树的差分技巧(结合LCA)计算每种颜色对边的“必须保留”标记:对颜色c的节点按DFS序排序,相邻节点及其LCA构成的路径上的边需保留,通过差分数组标记。
-
计算最大切割数:
- 遍历对偶树的所有边,统计未被任何颜色标记为“必须保留”的边数,即为最大切割数。
三、完整代码
#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; }四、代码关键步骤解析
-
对偶树构建:
- 对于每个三角形的三个顶点,先排序生成有序对(确保对角线表示唯一),用哈希表
M记录每条对角线对应的三角形节点。 - 若两条三角形共享某条对角线,则在对偶树中对应的节点之间添加边。
- 对于每个三角形的三个顶点,先排序生成有序对(确保对角线表示唯一),用哈希表
-
LCA预处理与DFS序:
- 对於对偶树,进行DFS遍历,记录每个节点的DFS序
dfn、深度dep,并预处理LCA的倍增数组F,用于快速计算任意两个节点的最近公共祖先。
- 对於对偶树,进行DFS遍历,记录每个节点的DFS序
-
颜色连通性标记:
- 对每种颜色,将其对应的节点按DFS序排序,相邻节点及其LCA构成的路径是保持该颜色连通的必要边。
- 利用差分数组
f标记路径:f[x]++、f[pre]++、f[lca] -= 2,后续通过子树求和得到每条边被标记的次数(次数>0表示必须保留)。
-
计算最大切割数:
- 对於对偶树进行子树求和,得到每条边的保留标记次数。
- 对偶树总边数为
(n-2)-1 = n-3(树的边数=节点数-1,对偶树节点数为n-2),未被标记(f[i]==0)的边可切割,统计其数量即为答案。
五、复杂度分析
- 时间复杂度:。其中对偶树构建为,LCA预处理为,每种颜色的节点排序与路径标记为(总节点数为),子树求和为。
- 空间复杂度:。用于存储对偶树、哈希表、LCA数组、差分数组等,可满足的约束。
- 1
信息
- ID
- 5566
- 时间
- 2000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者