【动态规划】友好城市

友好城市题解

题目传送门

友好城市 - AcWing

一、题目重述

Palmia国有一条横贯东西的大河,南北两岸各有N个位置不同的城市。北岸每个城市在南岸有且仅有一个友好城市,且这些配对各不相同。现在要在这些友好城市对之间建立直线航道,要求任意两条航道不能相交。求最多能批准多少条航道的建设申请。

二、题目分析

这个问题可以转化为:在给定的城市对中,选择尽可能多的对,使得这些对按照某一岸排序后,另一岸的坐标是严格递增的。这样就能保证航道不会相交。

三、问题思考

  1. 先将所有城市对按照某一岸(如北岸)的坐标升序排序
  2. 问题转化为在另一岸(南岸)的坐标序列中寻找最长上升子序列(LIS)
  3. 因为如果南岸坐标也是严格递增的,那么航道就不会相交

四、动态规划思路

a. 状态表示

  • f[i]:表示以第i个城市对的南岸坐标为结尾的最长上升子序列长度

b. 初始化

  • 每个城市对自身至少可以形成长度为1的子序列

c. 状态转移

  • 对于每个i,遍历所有j < i
  • 如果a[i].second > a[j].second,则f[i] = max(f[i], f[j]+1)

d. 最终结果

  • 所有f[i]中的最大值

五、代码实现

1. 朴素动态规划解法(O(n²))

#include 
using namespace std;
typedef pair<int, int> PII;
const int N = 5010;
PII a[N];
int f[N];

void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i].first >> a[i].second;
    }
    sort(a + 1, a + 1 + n); // 按北岸坐标排序
    
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        f[i] = 1; // 初始化为1
        for (int j = 1; j < i; j++) {
            if (a[i].second > a[j].second) { // 如果南岸坐标递增
                f[i] = max(f[i], f[j] + 1);
            }
        }
        ans = max(ans, f[i]); // 更新最大值
    }
    cout << ans << "\n";
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    solve();
    return 0;
}

2. 贪心+二分优化解法(O(nlogn))

#include 
using namespace std;
typedef pair<int, int> PII;
const int N = 5010;
PII a[N];
int f[N];
int cnt = 0; // 记录当前LIS长度

void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i].first >> a[i].second;
    }
    sort(a + 1, a + 1 + n); // 按北岸坐标排序
    
    f[++cnt] = a[1].second; // 初始化第一个元素
    for (int i = 2; i <= n; i++) {
        if (a[i].second > f[cnt]) { // 如果比当前序列最后一个大
            f[++cnt] = a[i].second; // 直接加入序列
        } else {
            // 找到第一个≥a[i].second的位置并替换
            *lower_bound(f + 1, f + 1 + cnt, a[i].second) = a[i].second;
        }
    }
    cout << cnt << "\n";
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    solve();
    return 0;
}

六、重点细节

  1. 排序的必要性:必须先对北岸坐标排序,才能将问题转化为南岸的LIS问题
  2. 航道不相交的条件:必须保证选中的城市对在两边的坐标都是严格递增的
  3. 二分优化:使用lower_bound来找到第一个≥当前值的位置,保证序列尽可能长
  4. 初始化:f数组初始只包含第一个元素,cnt从1开始

七、复杂度分析

  1. 朴素解法

    • 时间复杂度:O(n²) - 双重循环
    • 空间复杂度:O(n) - 只需要存储f数组
  2. 优化解法

    • 时间复杂度:O(nlogn) - 排序O(nlogn),二分查找O(logn)
    • 空间复杂度:O(n) - 存储f数组

八、总结

本题的关键在于问题转化,将航道不相交的条件转化为求最长上升子序列问题。通过排序和动态规划(或贪心+二分)可以高效解决。对于N=5000的数据规模,两种方法都能通过,但优化解法效率更高。

你可能感兴趣的:(Acwing算法课学习笔记记录,动态规划,算法,c++,学习)