基础算法acwing刷题指南6

目录:

786. 第k个数

787. 归并排序

789. 数的范围

AcWing 795. 前缀和 

797. 差分

双指针算法

799. 最长连续不重复子序列

800. 数组元素的目标和

位运算

801. 二进制中1的个数

离散化

802. 区间和

786. 第k个数

给定一个长度为 n 的整数数列,以及一个整数 k,请用快速选择算法求出数列从小到大排序后的第 k 个数

输入格式

第一行包含两个整数 n和 k。

第二行包含 n 个整数(所有整数均在10^9范围内),表示整数数列。

输出格式

输出一个整数,表示数列的第 k 小数。

输入样例:

5 3
2 4 1 5 3

输出样例:

3

学习了STL中的sort就应该实践一下

#include

using namespace std;

const int N = 1e5 + 10;

int n, m;
int q[N];

int main()
{
    cin >> n >> m;
    for(int i = 0; i < n; i ++) cin >> q[i];

    sort(q, q + n);
    cout << q[m - 1] << endl;

    return 0;
}

快排:

#include
using namespace std;
const int N = 1e5 + 10;
int n, m;
int q[N];

void quick_sort(int q[], int l, int r)
{
    if(l >= r) return;

    int x = q[l + r >> 1], i = l - 1, j = r + 1;

    while(i < j) 
    {
        do i ++; while(q[i] < x);
        do j --; while(q[j] > x);

        if(i < j) swap(q[i], q[j]);
    }

    quick_sort(q, l, j);
    quick_sort(q, j + 1, r);
}//这种方法好记很多

int main()
{
    cin >> n >> m;
    for(int i = 0; i < n; i ++) cin >> q[i];

    quick_sort(q, 0, n - 1);

    cout << q[m - 1] << endl;

    return 0;
}

787. 归并排序

给定你一个长度为 n 的整数数列。

请你使用归并排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式

输入共两行,第一行包含整数 n。

第二行包含n 个整数(所有整数均在1~10^9范围内),表示整个数列。

输出格式

输出共一行,包含 n 个整数,表示排好序的数列。

输入样例:

5
3 1 2 4 5

输出样例:

1 2 3 4 5

#include
using namespace std;
const int N = 100500;
int a[N],tmp[N];
void merge_sort(int q[],int l,int r)
{
    if(l >= r)return;
    
    int mid = l+r>>1;
    
    merge_sort(q,l,mid);
    merge_sort(q,mid+1,r);
    
    int k = 0,i = l,j = mid+1;
    while(i <= mid && j <= r)
    if(q[i] <= q[j]) tmp[k++] = q[i++];
    else tmp[k++] = q[j++];


    while(i <= mid) tmp[k++] = q[i++];
    while(j <= r) tmp[k++] = q[j++];
    
    for(i = l,j = 0;i <= r;i++,j++) q[i] = tmp[j];
}

//板子,记下就好
int main()
{
    int n;
    cin>>n;
    for(int i = 0;i < n;i++)cin>>a[i];
    merge_sort(a,0,n-1);
    
    for(int i = 0;i < n;i++)cout<     return 0;
}

求解逆序对问题,实际上有三种算法可以处理,分别是冒泡算法,归并排序,以及树状数组求解.

这里显然我们可以用性价比最高,代码最好写,效率特高的归并排序算法。

我们要注意一点,就是当我们发现填充第二个数组中的数,加入备用数组的使用,都要统计mid−i+1,因为此时此刻,我们第一个数组中剩余的所有数,都会和它构成逆序对。

去网站上去看数据范围,容易爆,于是我们开long long

#include

using namespace std;

typedef long long LL;

const int N = 1e5 + 10;

int a[N], tmp[N];

LL merge_sort(int q[], int l, int r)
{
    if (l >= r) return 0;

    int mid = l + r >> 1;

    LL res = merge_sort(q, l, mid) + merge_sort(q, mid + 1, r);

    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
        else
        {
            res += mid - i + 1;
            tmp[k ++ ] = q[j ++ ];
        }
    while (i <= mid) tmp[k ++ ] = q[i ++ ];
    while (j <= r) tmp[k ++ ] = q[j ++ ];

    for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];

    return res;
}

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);

    cout << merge_sort(a, 0, n - 1) << endl;

    return 0;
}

789. 数的范围

本题是练习二分很好的一道题目,二分程序虽然简单,但是如果写之前不考虑好想要查找的是什么,十有八九会是死循环或者查找错误,就算侥幸写对了也只是运气好而已。用二分去查找元素要求数组的有序性或者拥有类似于有序的性质,对本题而言,一个包含重复元素的有序序列,要求输出某元素出现的起始位置和终止位置,翻译一下就是:在数组中查找某元素,找不到就输出-1,找到了就输出不小于该元素的最小位置和不大于该元素的最大位置。所以,需要写两个二分,一个需要找到>=x的第一个数,另一个需要找到<=x的最后一个数。查找不小于x的第一个位置

#include
using namespace std;

const int N = 100001;
int n,m;
int q[N];
int main()
{
    cin>>n>>m;
    for(int i = 0;i < n;i++)cin>>q[i];
    
    while(m--)
    {
        int x;
        cin>>x;
        int l = 0,r = n-1;
        while(l < r)
        {
            int mid = l+r>>1;
            if(q[mid] >= x)r = mid;
            else l = mid + 1;
        }
        
        if(q[l] != x)cout<<"-1 -1"<         else 
        {
            cout<             
            int l = 0,r = n - 1;
            while(l < r)
            {
                int mid = l+r+1>>1;
                if(q[mid] <= x)l = mid;
                else r = mid - 1;
            }
            cout<         }
    }
    return 0;
}

AcWing 795. 前缀和 

输入一个长度为 n的整数序列。

接下来再输入 m 个询问,每个询问输入一对 l,r。

对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。

输入格式

第一行包含两个整数 n 和 m。

第二行包含 n 个整数,表示整数数列。

接下来 m 行,每行包含两个整数 l 和 r,表示一个询问的区间范围。

输出格式

共 m 行,每行输出一个询问的结果。

输入样例:

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

输出样例:

3
6
10

第一种:

#include
using namespace std;
const int N = 100010;
int a[N],s[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i = 1;i <= n;i++) cin>>a[i];
    
    for(int i = 1;i <= n;i++) s[i] = s[i-1] + a[i];
    while(m --)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\n",s[r] - s[l - 1]);
    }
    return 0;
}

第二种:

#include
using namespace std;
const int N = 100010;
int a[N],s[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i = 0;i < n;i++) cin>>a[i];

for(int i = 1;i <= n;i++) s[i] = s[i-1] + a[i-1];
while(m--)
{
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",s[r] - s[l - 1]);
}
return 0;
}

797. 差分

输入一个长度为n 的整数序列。

接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r]之间的每个数加上 c。

请你输出进行完所有操作后的序列。

输入格式

第一行包含两个整数 n 和 m。

第二行包含 n 个整数,表示整数序列。

接下来 m 行,每行包含三个整数 l,r,c表示一个操作。

输出格式

共一行,包含 n 个整数,表示最终序列。

输入样例:

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

输出样例:

3 4 5 3 4 2

#include
using namespace std;
const int N = 100010;
int a[N],b[N];
void insert(int l,int r,int c)
{
    b[l] += c;
    b[r+1] -= c;
}//差分最重要的一点

//看图

基础算法acwing刷题指南6_第1张图片int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++)cin>>a[i];
    for(int i = 1;i <= n;i++)insert(i,i,a[i]);
    
    while(m--)
    {
        int l,r,c;
        scanf("%d%d%d",&l,&r,&c);
        insert(l,r,c);
    }
    for(int i = 1;i<=n;i++)b[i] += b[i-1];
    
    for(int i = 1;i <= n;i++)printf("%d ",b[i]);
    return 0;

799. 最长连续不重复子序列

给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。

输入格式

第一行包含整数n 。

第二行包含 n 个整数(均在 0∼10^5 范围内),表示整数序列。

输出格式

共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。

输入样例:

5
1 2 2 3 5

输出样例:

3

#include
using namespace std;
const int N = 1e6+10;
int a[N],s[N];
int n;
int main()
{
    scanf("%d",&n);
    for(int i = 0;i < n;i++)scanf("%d",&a[i]);
    int res = 0;
    for(int i = 0,j = 0;i < n;i++)
    {
        s[a[i]]++;//向右扩展区间右端点

 //当扩展完区间右端点之后,有可能这个元素q[i]会有重复,下面这个while循环就是用来去除重复
        //去重只有一个办法,就是收缩区间左端点,同时收缩时要保证j是小于i的

        while(j < i && s[a[i]] > 1)s[a[j++]]--;//区间数量减一,之后区间端点右移
        res = max(res,i-j+1);//完成上述扩展和收缩之后,该区间是一个满足要求的区间,记录一下长度
    }
    cout<     return 0;
}

800. 数组元素的目标和

给定两个升序排序的有序数组 A 和 B,以及一个目标值 x。

数组下标从 0 开始。

请你求出满足 A[i]+B[j]=x 的数对 (i,j)。

数据保证有唯一解。

输入格式

第一行包含三个整数 n,m,x,分别表示 A 的长度,B 的长度以及目标值x。

第二行包含 n 个整数,表示数组 A。

第三行包含 m 个整数,表示数组 B。

输出格式

共一行,包含两个整数 i 和 j。

输入样例:

4 5 6
1 2 4 7
3 4 6 8 9

输出样例:

1 1

基础算法acwing刷题指南6_第2张图片

算法1
(双指针) O(n)
i从 0开始 从前往后遍历
j从 m - 1开始 从后向前遍历

和纯暴力的O(n^2)算法的区别就在于j指针不会回退

#include

using namespace std;

const int N = 1e6+10;

int a[N],b[N];

int count = 0;

int n,m,x;

int main()

{

scanf("%d%d%d",&n,&m,&x);

for(int i = 0;i < n;i++)scanf("%d",&a[i]);

for(int j = 0;j < m;j++)scanf("%d",&b[j]);

for(int i = 0,j = m - 1;i < n;i++)

{

while(j >= 0 && a[i]+b[j] > x)j--;

if(j >= 0 && a[i]+b[j] == x)cout<

}

return 0;

}

算法1
(lowbit) O(nlogn)
使用lowbit操作,进行,每次lowbit操作截取一个数字最后一个1后面的所有位,每次减去lowbit得到的数字,直到数字减到0,就得到了最终1的个数,

lowbit原理
根据计算机负数表示的特点,如一个数字原码是10001000,他的负数表示形势是补码,就是反码+1,反码是01110111,加一则是01111000,二者按位与得到了1000,就是我们想要的lowbit操作

 801. 二进制中1的个数

给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。

输入格式

第一行包含整数 n。

第二行包含 n个整数,表示整个数列。

输出格式

共一行,包含 n个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1 的个数。

输入样例:

5
1 2 3 4 5

输出样例:

1 1 2 1 2

#include
using namespace std;
const int N = 100010;

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

int main()
{
    int n;
    scanf("%d",&n);
   while(n--)
   {
       int x;
       cin>>x;
       int res = 0;
       while(x) x -= lowbit(x),res++;
       printf("%d ",res);
   }
    return 0;
}


802. 区间和

假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。现在,我们首先进行 n次操作,每次操作将某一位置 x 上的数加 c。

接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和。

输入格式

第一行包含两个整数 n 和 m,接下来 n 行,每行包含两个整数 x 和 c。再接下来 m 行,每行包含两个整数 l 和 r。

输出格式共 m行,每行输出一个询问中所求的区间内数字和。

 

输入样例:

3 3
1 2
3 6
7 5
1 3
4 6
7 8

输出样例:

8
0
5

#include
#include
#include
using namespace std;
int n,m;
typedef pairPII;
vectoralls;
vector add,query;
const int N = 300010;//n+2m的数据(两个操作,分别需要一个数和一对数的数据范围)
int a[N],s[N];

//因为这个数组是有序的,所以我们用二分
int find(int x)
{
    int l = 0,r = alls.size() - 1;
    while(l < r)
    {
        int mid = l + r >> 1;
        if(alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return l+1;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 0;i < n;i++)
    {
    int x,c;
    scanf("%d%d",&x,&c);
    add.push_back({x,c});
    alls.push_back(x);
    }
    
    for(int i = 0;i < m;i++)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        query.push_back({l,r});
        
        alls.push_back(l);
        alls.push_back(r);
    }
    //对已经映射到alls数组里面的数进行排序
    sort(alls.begin(),alls.end());
    //可能有重复我们将它去重
    alls.erase(unique(alls.begin(),alls.end()),alls.end());
    
    
    //进行完以上操作我们得遍历一遍
    for(auto item:add)
    {
        int x = find(item.first);
        a[x] += item.second;
    }
    for(int i = 1;i <= alls.size();i++)s[i] = s[i-1]+a[i];
    //处理求和啦
    //需要用到前面学的前缀和
    for(auto item: query)
    {
        int l = find(item.first),r = find(item.second);
        cout<     }
    
    return 0;
}

你可能感兴趣的:(OI新手入门刷题,算法,c++)