CF#771 Div.2 A~C 题解

这篇博客探讨了三种不同的算法问题,涉及序列的最小字典序排列、奇数偶数排序以及逆序对的连边问题。通过贪心策略解决A题,将序列翻转以达到最小字典序;B题中,通过判断奇数和偶数子序列的单调性确定是否能形成单调不降序列;C题利用单调栈处理逆序对,有效地计算连通块数量。这些方法展示了在算法设计中如何利用数据结构优化复杂度。

CF #771 div.2

A. Reverse(思维)

题目大意

给定长度为n的序列 可以翻转任意一段 输出他的最小字典序排列

解题思路

因为要最小字典序 所以从n = 1开始向后枚举 如果说q[i] != i 那就说明把 i 换回这里字典序最小(贪心

很重要的一点是因为我的程序鲁棒性不够强 所以导致了数组越界 达到了负的下标

以后一定要注意在模拟reverse的时候不能越界

代码
int q[510], n;

void solve()
{
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> q[i];

    int j = 1;
    while(q[j] == j)    j ++;
    int k = n;
    while(q[k] != j && k > 0)    k --;
    for(int i = j, l = k; i < l; i ++, l --)
        swap(q[i], q[l]);
    
    for(int i = 1; i <= n; i++) cout << q[i] << " ";
    cout << endl;
}   

B. Odd Swap Sort(构造)

题目大意

给定长度为n的数组 当ai + ai+1是奇数时 你可以对其调换位置 这样的操作不限次数 问是否能够将序列变成单调不降序列(ai <= ai+1)

解题思路

ai + ai+1是奇数 代表我们只能调换不同奇偶性的两个相邻元素

所以可以把数组中的元素分为奇数和偶数两个部分

可以发现,如果奇数数组中或是偶数数组中有逆序的情况,是无法通过题目中的特定操作来把数组变为不严格的升序数组的

代码
int n;
vector<int> o, e;//odd even

void solve()
{
    o.clear();e.clear();
    scanf("%d", &n);
    for(int i = 0; i < n; i++)
    {
        int x;scanf("%d", &x);
        if(x & 1)   o.push_back(x);
        else e.push_back(x);
    }

    bool flag = true;
    for(int i = 1; i < o.size(); i++)
    {
        if(o[i] < o[i - 1])
        {
            flag = false;
            break;
        }
    }

    for(int i = 1; i < e.size(); i++)
    {
        if(e[i] < e[i - 1]) 
        {
            flag = false;
            break;
        }
    }   
    if(flag)    puts("YES");
    else puts("NO");
}   

英语积累:non-decreasing order 单调不降序列

C. Inversion Graph(单调栈)

题目大意

为一个长度为n的序列的逆序对连边(如果后面的数小于前面的数,就将这两个数(包括这两个数所在的连通块), 求连通块的个数

解题思路

逆序对

如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。

如果用朴素的做法 就是双循环 但test = 2e5 显然只能使用nlong或者n的算法

我们来观察一下样例

可以发现后一个连通块中的所有数大于前一个连通块中的所有数

以连通块(也就是集合)的角度来思考,如果一个数小于前面连通块的最大数,则将这个数添加至连通块中,否则,这个数作为新的连通块

选取样例

3 2 1 6 5 4
得到的栈 3 - 3 - 3 - 3,6 - 3,6 - 3,6

此时还未万事大吉
3 1 5 2 4
若是按上面的方法分析 3 - 3 - 3,5 - 3,5 - 3,5
但是 答案应该是1 因为2连边到3和5 3和5应该是在一个连通块中的
得出结论:当一个数属于当前连通块时,还应该继续判断是否属于前面的连通块
代码
void solve()
{
    stack<int> stk;// 用单调栈维护每个连通块中最大的元素

    cin >> n;
    for(int i = 0; i < n; i++)
    {
        int x;
        cin >> x;

        if(stk.size() == 0)  stk.push(x);
        else // stk.size()
        {
            if(x > stk.top())   stk.push(x);// 大于当前栈顶直接入栈
            else // 否则保存栈顶,将栈中大于这个数的元素出栈,然后将原栈顶重新入栈
            {
                int t = stk.top();
                while(stk.size() && t > x)  stk.pop();
                stk.push(t);
            }
        }
    }
    cout << stk.size() << endl;
}
### 单调栈算法概述 单调栈是一种特殊的栈数据结构,其核心在于通过维护一个具有单调性质(递增或递减)的栈来优化某些特定问题的解决方案。这种技术可以显著降低时间复杂度至 \( O(n) \)[^5]。 #### 基本原理 当处理数组中的元素时,如果当前元素破坏了栈内的单调性,则会弹出栈顶直到恢复单调性为止[^4]。这一特性使得单调栈非常适合用于寻找某个位置左侧或右侧第一个大于或小于它的值的问题。 --- ### 应用场景 1. **柱状图最大矩形面积** 给定一组高度不等的直方条,目标是找出这些直方条所能围成的最大矩形区域大小。此问题是单调栈的经典应用之一[^2]。 2. **股票价格跨度计算** 对于每日股价记录,需快速查询某一天之前连续多少天的价格均低于当天价格。利用单调递减栈可高效完成此类任务。 3. **最近较小/较大值查找** 需要确定每个元素左右两侧距离最近且满足条件(更小或者更大)的位置索引情况,在图像处理等领域有广泛用途。 --- ### 实现方式 以下是基于 Python 的具体实现案例: #### 求解柱状图中最大的矩形面积 ```python def largestRectangleArea(heights): stack = [] # 使用列表模拟栈操作 heights.append(0) # 添加哨兵节点简化边界判断逻辑 max_area = 0 i = 0 while i < len(heights): if not stack or heights[i] >= heights[stack[-1]]: stack.append(i) i += 1 else: top_index = stack.pop() width = i if not stack else (i - stack[-1] - 1) area = heights[top_index] * width max_area = max(max_area, area) return max_area ``` 上述代码片段展示了如何运用单调栈解决柱状图最大矩形面积问题。其中关键是借助辅助变量 `width` 来动态调整宽度范围并更新全局最优解。 #### 构建单调递减栈示例 考虑输入序列为 `[9,2,7,1,6,4,3,5]` ,下面演示创建一个单调递减栈的过程[^3]: ```python nums = [9, 2, 7, 1, 6, 4, 3, 5] decreasing_stack = [] result = [] for num in nums: while decreasing_stack and num > decreasing_stack[-1]: result.append(decreasing_stack.pop()) decreasing_stack.append(num) while decreasing_stack: result.append(decreasing_stack.pop()) print(result) # 输出最终结果顺序反映原序列经过单向筛选后的形态特征 ``` 这里我们遍历整个数组,并按照既定规则不断调整内部状态直至结束循环得到预期输出形式。 --- ### 总结说明 通过对以上理论阐述以及实际编码演练可以看出,掌握好单调栈技巧不仅有助于提升程序运行效率还能增强解决问题的能力水平。希望读者能够深入理解背后蕴含的思想精髓从而灵活应用于更多场合之中!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值