归并排序(求逆序对),快速排序(求序列第k小的数,最大子串和)

归并:区间一分二,将两个子区间排序,合并 o(nlogn)
快排:选一基准,将小于基准的放左边,大于放右边,然后。。
时间复杂度nlogn~n^2

归并 & 求逆序对

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

int ar[500050], br[500050];
ll ans;
int n;
int p1, p2;

void erfen(int l, int r)
{
    if(l == r) return ;
    int mid = (l + r) >> 1;
    int i = l, j = mid + 1, k = l;
    erfen(l, mid), erfen(mid + 1, r);
    while(i <= mid && j <= r)
    {
        if(ar[i] <= ar[j]) br[k++] = ar[i++];
        else
        {
            br[k++] = ar[j++];
            ans += mid - i + 1;
        }
    }
    while(i <= mid) br[k++] = ar[i++];
    while(j <= r) br[k++] = ar[j++];
    for(int i = l; i <= r; ++i) ar[i] = br[i];
}

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) scanf("%d", &ar[i]);
    erfen(1, n);
    printf("%lld\n", ans);
    return 0;
}

树状数组求逆序对

https://www.luogu.com.cn/problem/solution/P1908

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

int tree[500005];
int val[500005];
int n;
ll ans;
struct node
{
    int x, pos;
}ar[500050];

int lowbit(int x)
{
    return x & -x;
}

bool cmp(node a, node b)
{
    if(a.x != b.x) return a.x < b.x;
    else return a.pos < b.pos;
}

void build(int p)
{
    for(int i = p; i <= n; i += lowbit(i)) ++tree[i];
}

ll query(int p)
{
    ll res = 0;
    for(int i = p; i > 0; i -= lowbit(i)) res += tree[i];
    return res;
}

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
    {
        scanf("%d", &ar[i].x);
        ar[i].pos = i;
    }
    sort(ar + 1, ar + n + 1, cmp);
    for(int i = 1; i <= n; ++i) val[ar[i].pos] = i;
    for(int i = 1; i <= n; ++i)
    {
        build(val[i]);
        ans += i - query(val[i]);
    }
    printf("%lld\n", ans);
    return 0;
}

归并模板

(long long ago 写的,效率不如上面的欸)

//归并
#include <bits/stdc++.h>

using namespace std;

int a[100], b[100];//原数组a,数组b用于暂存排好序的原数组

void _merge(int l, int mid, int r)//合并
{
    int p1 = l, p2 = mid + 1;//两个指针
    for(int i = l; i <= r; ++i)//遍历数组
    {
        //当前数组b应该取那个数?左半边还是右半边
        //当p1小于mid时(即左半边还有数)且 p2 大于右边界时(右半边没数了)或者左边这个数小于右边这个数时
        //应该取左边的数,即a[p1],并将p1右移
        if((p1 <= mid) && ((p2 > r) || a[p1] <= a[p2]))
        {
            b[i] = a[p1];
            p1++;
        }
        //否则取右边
        else
        {
            b[i] = a[p2];
            p2++;
        }
    }
    for(int i = 1; i <= r; ++i) a[i] = b[i];//还原给原数组
}

void erfen(int l, int r)//分区间
{
    int mid = (l + r) / 2;
    if(l < r)
    {
        erfen(l, mid);
        erfen(mid + 1, r);
    }
    _merge(l, mid, r);
}

int main()
{
    for(int i = 1; i <= 10; ++i)    cin >> a[i] ;
    erfen(1, 10);
    for(int i = 1; i <= 10; ++i)    cout << a[i] << endl;
    return 0;
}

快排模板

//快排
#include <bits/stdc++.h>

using namespace std;

int a[105];

void quick_sort(int l, int r)
{
    int i = l, j = r;
    //i:从左向右找第一个>x的数;j:从右向左找第一个<x的数
    int mid = (l + r) / 2;
    int x = a[mid];//基准选正中间这个数
    while(i <= j)
    {
        //注意木有等号,因为基准并不一定排好序后也正在中间,所以基准有可能也要被交换
        while(a[i] < x) i++;
        while(a[j] > x) j--;
        if(i <= j)
        {
            swap(a[i], a[j]);
            ++i;
            --j;
        }
    }
    if(l < j)   quick_sort(l, j);
    if(i < r)   quick_sort(i, r);
}

int main()
{
    for(int i = 1; i <= 10; ++i)    cin >> a[i];
    quick_sort(1, 10);
    for(int i = 1; i <= 10; ++i)    cout << a[i] << endl;
    return 0;
}

归并求逆序对个数

在这里插入图片描述
最简单的,冒泡排序中交换的次数就是逆序对个数,但是冒泡时间复杂度过于太高,不用那个法

//归并求逆序对个数
#include <bits/stdc++.h>

using namespace std;

int a[100], b[100];
int cnt;

void _merge(int l, int mid, int r)
{
    int p1 = l, p2 = mid + 1;
    for(int i = l; i <= r; ++i)
    {
        if((p1 <= mid) && ((p2 > r) || a[p1] <= a[p2]))
        {
            b[i] = a[p1];
            p1++;
        }
        //如果取的数来自右半边,那么这就摧毁了mid - p1 + 1个逆序对
        //即序列中,由这个数参与的逆序对个数为mid - p1 + 1个
        else
        {
            b[i] = a[p2];
            p2++;
            cnt += mid - p1 + 1;
            //就加了这一句
        }
    }
    for(int i = 1; i <= r; ++i) a[i] = b[i];
}

void erfen(int l, int r)
{
    int mid = (l + r) >> 1;
    if(l < r)
    {
        erfen(l, mid);
        erfen(mid + 1, r);
    }
    _merge(l, mid, r);
}

int main()
{
    for(int i = 1; i <= 10; ++i)    cin >> a[i] ;
    erfen(1, 10);
    cout << cnt << endl;
    return 0;
}

快排求序列第k小的数

不用排序,因为这个序列里有很多数,排序会超时
但是用了快排的思想
随便从题干里取了个快读板子

//快排求序列第k小的数
#include <bits/stdc++.h>

using namespace std;

int ar[5000050];
int t, n, k;

inline int read(){
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9'){
        x = (x<<1) + (x<<3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

int finding(int l, int r, int k)
{
    if(l == r)  return ar[l];
    int i = l, j = r;
    int mid = (l + r) / 2;
    int x = ar[mid];
    while(i <= j)
    {
        while(ar[i] < x) i++;
        while(ar[j] > x) j--;;
        if(i <= j)
        {
            swap(ar[i], ar[j]);
            ++i;
            --j;
        }
    }
    if(k <= j)  return finding(l, j, k);
    else if(i <= k) return finding(i, r, k);
    else    return ar[k];
}

int main()
{
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d %d", &n, &k);
        for(int i = 1; i <= n; ++i) ar[i] = read();
        printf("%d\n", finding(1, n, k));
    }
    return 0;
}

快读

inline int read(){
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9'){
        x = (x<<1) + (x<<3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

求最大子串和

方法一:动态规划
状态转移方程:

	if(current <= 0)    current = ar[i];
    else    current += ar[i];
    mx = max(current, mx);
//最大子串和 动态规划
#include <bits/stdc++.h>

using namespace std;

int ar[100];
int mx = -999999999, current;

int main()
{
    for(int i = 1; i <= 10; ++i) cin >> ar[i];
    current = ar[1];
    for(int i = 2; i <= 10; ++i)
    {
        if(current <= 0)    current = ar[i];
        else    current += ar[i];
        mx = max(current, mx);
    }
    cout << mx << endl;
    return 0;
}

方法二: 非动态规划
这个最长的字串和可能来自三种情况:
1.左半串最长和
2.右半串最长和
3.横跨中间的某个串的和
用递归求前两种,第三种需要求左半串的后缀和的最大值,右半串的前缀和的最大值,将这两个最大值相加即可。

//最长子串和 fei动态规划
#include <bits/stdc++.h>

using namespace std;

int ar[105];

int maxs(int n, int l, int r)
{
    int mid = (l + r) / 2;
    if(l == r)  return ar[l];//递归中间,这个串就一个元素了
    int ans = max(maxs(n, l, mid), maxs(n, mid + 1, r));//分别递归取左半串和右半串的最大值然后对这两个取max
    int ll = 0, rr = 0, tmp = 0;
    //ll代表左半串后缀和的最大值,rr代表右半串前缀和的最大值
    for(int i = mid + 1; i <= r; ++i)
    {
        tmp += ar[i];
        rr = max(rr, tmp);
    }
    tmp = 0;
    for(int i = mid; i >= l; --i)
    {
        tmp += ar[i];
        ll = max(ll, tmp);
    }
    ans = max(ans, ll + rr);//第三种情况与前两种情况的最大值再取一个max就是答案
    return ans;
}

int main()
{
    for(int i = 1; i <= 10; ++i) cin >> ar[i];
    cout << maxs(10, 1, 10) << endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值