《算法竞赛从入门到国奖》算法基础:入门篇-分治

💡Yupureki:个人主页

✨个人专栏:《C++》 《算法》


🌸Yupureki🌸的简介:


目录

前言

[1. 逆序对](#1. 逆序对)

算法原理

实操代码

[2. 求第 k 小的数](#2. 求第 k 小的数)

算法原理

实操代码

[3. 最大子段和](#3. 最大子段和)

算法原理

实操代码

[4. 地毯填补问题](#4. 地毯填补问题)

算法原理

实操代码


前言

分治全称为分而治之。指把一个大问题转化为几个小的子问题,把所有的子问题解决完了,自然解决了整个大问题

1. 逆序对

题目链接:

P1908 逆序对 - 洛谷

算法原理

分治是解决[逆序对]问题最经典的做法

我们把原数组从中间mid一分为二

那么答案包含以下三种

  • 左边数组总逆序对个数a
  • 右边数组总逆序对个数b
  • 左数组选一个数,右数组选一个数,构成逆序对的总个数c

那么总个数ret = a + b + c

同理,我们继续划分,把左数组再一分为二讨论,右数组一分为二讨论,然后再划分......

直到数组无法划分(只有一个数)

当左数组答案和右数组答案得到时,要处理的是一左一右的情况,这个时候我们可以排序

例如排完序得到:

1 5 6 / 2 4 8

那么我可以利用双指针求解:

定义cur1从左往右遍历左数组,定义cur2从左往右遍历右数组,cnt为构成逆序对的个数

当v[cur1] > v[cur2]时,cur2++,cnt += (mid - cur1 + 1) ,因为当v[cur1] > v[cur2]时,也表明cur1之后的所有元素都大于v[cur2](因为单调递增)

当v[cur1] <= v[cur2]时,cur1++

实操代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

vector<int> v;

long long merge(int left,int right)
{
    if(left >= right)
        return 0;
    int mid = (left + right)/2;
    long long ret = 0;
    ret += merge(left,mid);
    ret += merge(mid+1,right);
    sort(v.begin() +left,v.begin() +mid + 1);
    sort(v.begin() +mid + 1,v.begin() + right + 1);
    int cur1 = left;
    int cur2 = mid + 1;
    while(cur1 <= mid && cur2 <=right)
    {
        if(v[cur1] <= v[cur2])
            cur1++;
        else
        {
            ret += mid - cur1 + 1;
            cur2++;
        }
    } 
    return ret;
}

int main()
{
    int n;cin>>n;
    for(int i = 0;i<n;i++)
    {
        int num;cin>>num;
        v.push_back(num);
    }
    cout<<merge(0,v.size()-1);
    return 0;
}

2. 求第 k 小的数

题目链接:

P1923 【深基9.例4】求第 k 小的数 - 洛谷

算法原理

我们使用快排的思想,进行三路划分

  • 定义区间[left,right],key为数组下标为left的元素的值
  • begin初始化为left,end初始为right,cur从begin + 1开始遍历
  • 当v[cur] < key,交换v[cur]和v[begin]的值,同时cur++,left++
  • 当v[cur] > key,交换v[cur]和v[end]的值,同时end--
  • 当v[cur] == key时,cur++
  • 当cur > right时,循环停止

这样保证一趟循环后,数组分为三个区间

即左区间全为小于key 的值,中间区间全为等于key 的值,右区间全为大于key的值

这样我们再看找第k小的值,就很好找了

假设左区间的个数为c1,中间区间的个数为c2

  • 如果k <= c1,那么整个数组第k小的数,一定在左边的区间
  • 如果c1 < k <= c1 + c2,那么k落在中间的区间,则第k小的数一定为key
  • 如果c1 + c2 < k,那么第k小的数一定在右边的区间,这个时候在右区间找第k - c1 - c2小的数即可

实操代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

vector<long long> v;

long long merge(int left, int right, int k) 
{
    int begin = left;int cur = begin + 1;
    int end = right;long long key = v[left];
    while(cur <= end)
    {
        if(v[cur] > key)
        {
            swap(v[cur],v[end]);
            end--;
        }
        else if(v[cur] < key)
        {
            swap(v[cur],v[begin]);
            begin++;
            cur++;
        }
        else
            cur++;
    }
    int c1 = begin - left;int c2 = end - begin + 1; 
    if(k <= c1)
        return merge(left,begin - 1,k);
    else if(k <= c1 + c2)
        return key;
    else
        return merge(end + 1,right,k - c1 - c2);
}

int main() 
{
    int n, k; 
    cin >> n >> k;
    k++;
    v.resize(n);
    for (int i = 0; i < n; i++) 
        scanf("%lld", &v[i]);
    cout << merge(0, n - 1, k) << endl;
    return 0;
}

3. 最大子段和

题目链接:

P1115 最大子段和 - 洛谷

算法原理

如果把整个序列[l,r]从中间mid位置分成两部分,那么整个序列中所有的子数组就分成三部分:

  • 子数组在区间[l,mid]内;
  • 子数组在区间[mid+1,r]内;
  • 子数组的左端点在[l,mid]内,右端点在[mid+1,r]内。

那么我们的「最终结果」也会在这三部分取到,要么在左边区间,要么在右边区间,要么在跨越中轴线的区间。因此,我们可以先求出左边区间的最大子段和,再求出右边区间的最大子段和,最后求出中间区间的最大子段和。其中求「左右区间」时,可以交给「递归」去解决。

那我们重点处理如何求出「中间区间」的最大子段和。可以把中间区间分成两部分

  • 左边部分是从mid为起点,「向左延伸」的最大子段和
  • 右边部分是从mid+1为起点,「向右延伸」的最大子段和

分别求出这两个值,然后相加即可。

实操代码

cpp 复制代码
#include <iostream>
using namespace std;
const int N = 2e5 + 10;
int n;
int a[N];
int dfs(int l, int r)
{
    if(l == r) return a[l];
    int mid = (l + r) >> 1;
    // 先求⼀下左右两边的最⼤值
    int ret = max(dfs(l, mid), dfs(mid + 1, r));
    int suml = a[mid], sumr = a[mid + 1];
    int maxl = suml, maxr = sumr;
    // 求出以 a[mid] 开始,向左延伸的最⼤值
    for(int i = mid - 1; i >= l; i--)
    {
        suml += a[i];
        maxl = max(maxl, suml);
    }
    // 求出以 a[mid + 1] 开始,向右延伸的最⼤值
    for(int i = mid + 2; i <= r; i++)
    {
        sumr += a[i];
        maxr = max(maxr, sumr);
    }
    // 返回三种情况的最⼤值
    return max(maxr + maxl, ret);
}
int main()
    {
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    cout << dfs(1, n) << endl;
    return 0;
}

4. 地毯填补问题

题目链接:

P1228 地毯填补问题 - 洛谷

算法原理

一维分治的时候我们是从把区间从中间切开,那二维分治我们如何处理?

我们把整个二维矩阵从中间切两刀,分成四个相等的小矩阵

由于公主在某个格子内,我们假设在左上角的矩阵内

我们处理分治的核心是:处理几个相同的子问题

而现在显然子问题不相同,因为只有一个小矩阵有公主

那么既然其他三个矩阵没公主,我们就分别创造一个假公主给其余的三个矩阵

根据地毯的形状,我们可以在中心处这样铺,这样其余的三个矩阵就都多出了一个额外的方块,然后我们假设这个方块是公主,就可以对4个矩阵继续分治了

当然公主也有可能不在左上角,但无论在哪,我们都有对应的铺法

一直分治直到矩阵只有1个方格即可

实操代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

void dfs(int a, int b, int len, int x, int y)
{
    if (len == 1)
        return;
    len /= 2;
    if (x < a + len && y < b + len)
    {
        cout << a + len << " " << b + len << " "<<"1" << endl;
        dfs(a, b, len, x, y);
        dfs(a, b + len, len, a + len - 1, b + len);
        dfs(a + len, b, len, a + len, b + len - 1);
        dfs(a + len, b + len, len, a + len, b + len);
    }
    else if (x < a + len && y >= b + len)
    {
        cout << a + len << " " << b + len - 1 << " " << "2" << endl;
        dfs(a, b, len, a + len - 1, b + len - 1);
        dfs(a, b + len, len, x, y);
        dfs(a + len, b, len, a + len, b + len - 1);
        dfs(a + len, b + len, len, a + len, b + len);
    }
    else if (x >= a + len && y < b + len)
    {
        cout << a + len - 1 << " " << b + len << " " << "3" << endl;
        dfs(a, b, len, a + len - 1, b + len - 1);
        dfs(a, b + len, len, a + len - 1, b + len);
        dfs(a + len, b, len, x, y);
        dfs(a + len, b + len, len, a + len, b + len);
    }
    else
    {
        cout << a + len - 1 << " " << b + len - 1 << " " << "4" << endl;
        dfs(a, b, len, a + len - 1, b + len - 1);
        dfs(a, b + len, len, a + len - 1, b + len);
        dfs(a + len, b, len, a + len, b + len - 1);
        dfs(a + len, b + len, len, x, y);
    }
}

int main()
{
    int k, x, y; cin >> k>>x >> y;
    int len = (1 << k);
    dfs(1, 1, len, x, y);
    return 0;
}
相关推荐
无心水2 小时前
4、Go语言程序实体详解:变量声明与常量应用【初学者指南】
java·服务器·开发语言·人工智能·python·golang·go
ZPC82102 小时前
psutil
开发语言·php
充值修改昵称2 小时前
数据结构基础:B*树B+树的极致优化
数据结构·b树·python·算法
jiunian_cn2 小时前
【C++】线程库
开发语言·c++
0x532 小时前
JAVA|智能仿真并发项目-并行与并发
java·开发语言
one____dream2 小时前
【算法】相同的树与对称二叉树
b树·python·算法·递归
e疗AI产品之路2 小时前
心电分析诊断算法评估方法介绍
算法·心电分析
爱编码的傅同学2 小时前
【今日算法】LeetCode 11.盛水最多的容器 15.三数之和 283.移动0
数据结构·算法·leetcode
啊我不会诶2 小时前
Codeforces Round 1072 (Div. 3)补题
笔记·学习·算法