题解:洛谷 P2610 旅游

【题目来源】

洛谷:P2610 [ZJOI2012] 旅游 - 洛谷

【题目描述】

到了难得的暑假,为了庆祝小白在数学考试中取得的优异成绩,小蓝决定带小白出去旅游~~

经过一番抉择,两人决定将 T 国作为他们的目的地。T 国的国土可以用一个凸 \(n\) 边形来表示,\(n\) 个顶点表示 \(n\) 个入境/出境口。T 国包含 \(n-2\) 个城市,每个城市都是顶点均为 \(n\) 边形顶点的三角形(换而言之,城市组成了关于 T 国的一个三角剖分)。两人的旅游路线可以看做是连接 \(n\) 个顶点中不相邻两点的线段

img/Pasted image 20260629085213.png

为了能够买到最好的纪念品,小白希望旅游路线上经过的城市尽量多。作为小蓝的好友,你能帮帮小蓝吗?

【输入】

每个输入文件中仅包含一个测试数据。

第一行包含一个正整数 \(n\)\(n\) 的含义如题目所述。

接下来有 \(n-2\) 行,每行包含三个整数 \(p,q,r\),表示该城市三角形的三个顶点的编号(T 国的 \(n\) 个顶点按顺时针方向从 \(1\)\(n\) 编号)。

【输出】

输出文件共包含一行,表示最多经过的城市数目。(一个城市被当做经过当且仅当其与线路有至少两个公共点

【输入样例】

6
1 2 4
2 3 4
1 4 5
1 5 6

【输出样例】

4

【核心思想】

  1. 问题分析:给定一个凸 \(n\) 边形的三角剖分(共 \(n-2\) 个三角形),用一条连接多边形两个不相邻顶点的线段(即一条对角线)去穿过这些三角形,要求最大化该线段穿过的三角形数量(一个三角形被计入当且仅当线段与它有至少两个公共点,即线段穿过其内部或沿其边经过)。本质是将对偶图转化为树,并在树上求最长路径的问题。

  2. 算法选择

    • 对偶图建树:将每个三角形视为一个节点,若两个三角形共享一条边(即该边是多边形内部的一条对角线),则在它们之间连一条无向边。由于三角剖分是对偶树,该图是一棵树。
    • 求树的直径(节点数):旅游路线(一条对角线)穿过的三角形序列正好对应这棵树上的一条简单路径。经过的三角形数量 = 路径上的节点数 = 路径边数 + 1。因此,问题等价于求这棵对偶树的直径(最长路径)的节点数。
    • 两次 DFS 求直径:任选一点出发,找到最远节点,再从该节点出发找到最远距离,即为直径长度(边数),加 1 即为最多经过的城市数。
  3. 关键步骤

    • 读入并建图:读入 \(n\)\(n-2\) 个三角形,对每个三角形的三条边(三个顶点对)进行排序统一表示,用 map<pair<int,int>, int> 记录每条边首次出现的三角形编号;若某条边第二次出现,则在当前三角形与首次出现的三角形之间加一条无向边。
    • 对偶树 DFS:以任意三角形(如编号 1)为起点,进行 DFS,记录每个节点到起点的距离(边数)和父节点,找到最远节点 p
    • 第二次 DFS:从 p 出发,再次 DFS,找到最远距离 maxd,即为对偶树的直径(边数)。
    • 输出答案maxd + 1(因为路径上的节点数比边数多 1)。
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log n)\),主要来自 map 查找和排序,\(n \le 2\times 10^5\),可接受。
    • 空间复杂度:\(O(n)\),存储邻接表和辅助数组。
  5. 对偶树与直径的核心思想

    • 三角剖分的对偶图是树:每个三角形是节点,共享边是边,该图无环且连通,故为树。
    • 对角线穿过的三角形序列在树上形成路径:任意一条多边形内部的线段依次穿过的三角形,必然在对偶树上形成一条简单路径。
    • 最大化三角形数量 = 树的最长路径节点数:直径路径上的节点数即为能穿过的最多三角形数,无需考虑具体端点,因为任意直径路径的两端对应两个顶点,它们之间的对角线正好穿过这些三角形。
    • 适用场景:适用于将几何剖分问题转化为图论树的最长路径问题,是“对偶图”思想的典型应用。

【算法标签】

普及+ #树形DP

【代码详解】

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;     // 用于map的键:存储两个顶点组成的边
const int N = 200005, M = N * 2;    // N: 最大城市数, M: 最大边数
int n;                          // n: 凸n边形的顶点数
int h[N], e[M], ne[M], idx;     // 链式前向星存图
int l, r, p, maxd;              // l: 直径左端点, r: 直径右端点, p: 临时最远点, maxd: 当前最大距离
int d[N];                       // d[u]: 从某个起点到城市u的距离
int pre[N];                     // pre[u]: DFS时城市u的父节点
map<PII, int> mp;               // mp[{a, b}]: 边(a,b)第一次出现的城市编号

// 链式前向星:添加无向边
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// DFS:求对偶树的直径端点,同时记录父节点信息
void dfs(int u, int fa)
{
    pre[u] = fa;                // 记录u的父节点

    // 遍历u的所有邻接城市
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int v = e[i];
        if (v == fa) continue;  // 跳过父节点

        d[v] = d[u] + 1;          // 更新v到起点的距离(对偶树中边权为1)

        // 更新最远点
        if (d[v] > maxd)
        {
            maxd = d[v];
            p = v;
        }

        dfs(v, u);              // 递归DFS
    }
}

int main()
{
    cin >> n;                   // 读入顶点数

    // 初始化链式前向星
    memset(h, -1, sizeof(h));

    // 读入n-2个三角形城市
    for (int i = 1; i <= n - 2; i++)
    {
        int p, q, r;
        cin >> p >> q >> r;

        // 将三个顶点排序,保证边的表示唯一(小边在前)
        if (p > q) swap(p, q);
        if (p > r) swap(p, r);
        if (q > r) swap(q, r);

        // 处理边(p,q):如果这条边第一次出现,记录城市编号
        // 如果第二次出现,说明两个城市共享这条边,在对偶树中建立连接
        if (!mp[{p, q}])
            mp[{p, q}] = i;
        else
            add(i, mp[{p, q}]), add(mp[{p, q}], i);

        // 处理边(p,r)
        if (!mp[{p, r}])
            mp[{p, r}] = i;
        else
            add(i, mp[{p, r}]), add(mp[{p, r}], i);

        // 处理边(q,r)
        if (!mp[{q, r}])
            mp[{q, r}] = i;
        else
            add(i, mp[{q, r}]), add(mp[{q, r}], i);
    }

    // ========== 求对偶树的直径 ==========
    // 对偶树:每个城市是一个节点,共享边的城市之间连边
    // 旅游路线经过的城市数 = 对偶树上某条路径的长度 + 1

    // 第一次DFS:从城市1出发,找到最远点p
    dfs(1, 0);

    l = p;                      // l是直径的一个端点
    d[l] = maxd = 0;            // 重置距离数组,准备第二次DFS

    // 第二次DFS:从l出发,找到最远点r
    dfs(l, 0);
    r = p;                      // r是直径的另一个端点

    // 输出答案:对偶树的直径 + 1
    // 直径d[r]表示经过的城市间隔数,+1表示经过的城市数量
    cout << d[r] + 1 << endl;

    return 0;
}

【运行结果】

6
1 2 4
2 3 4
1 4 5
1 5 6
4
posted @ 2026-09-01 23:09  团爸讲算法  阅读(6)  评论(0)    收藏  举报