算法刷题day16

引言

关于这个树状数组和线段树还是挺重要的,主要题目不会直接问你求哪个区间的和什么的,题目往往是给出一个真实的例子,隐藏这些操作条件,需要让你自己去抽象出来这些操作,然后再来判断到底用什么数据结构和算法,所以刷题的好处就在于此,有些题自己根本就想不到还能这样做,所以只有不断地见大量的习题才行。


一、小朋友排队

标签:贪心、树状数组、排序

思路1:这道题跟冒泡排序差不多所以就按冒泡排序做了做,超时了,只能过 5 / 11 5/11 5/11 的数据,但蓝桥杯的话还是能拿到分数的。
思路2:这道题首先是有 k k k 个逆序对,所以至少要交换 k k k 次,而由于冒泡排序就是交换 k k k 次,所以我们可以得知最优解肯定是交换 k k k 次。由于存在一种交换策略使得程度最小,并且如果一个数,前面有 k 1 k_1 k1 个数比它大,后面有 k 2 k_2 k2 个数比它小,那么这个数至少要移动 k 1 + k 2 k_1+k_2 k1+k2 次,那么所有的这样的数加起来就是 2 k 2k 2k 次,因为相当于每个逆序对都算了两次,又由于最优解是交换 k k k 次,那么整个数列的移动最优解就是 2 k 2k 2k 次,那么所以这个算法就成立。只要找到每个数前面有 k 1 k_1 k1 个数比它大,后面有 k 2 k_2 k2 个数比它小,就知道这个数移动了 k 1 + k 2 k_1+k_2 k1+k2 次,再根据高斯求和公式就能算出不高兴程度了。这个可以用树状数组动态的求前缀和,从前往后插,高度为下标,元素为人数,每次查找 h [ i ] h[i] h[i] 后面有多数人,就能知道前面有多少人更大,从后往前插,每次查找 h [ i ] h[i] h[i] 前面的人就知道后面有多少人比它小。

题目描述:

n 个小朋友站成一排。

现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友。

每个小朋友都有一个不高兴的程度。

开始的时候,所有小朋友的不高兴程度都是 0。

如果某个小朋友第一次被要求交换,则他的不高兴程度增加 1,如果第二次要求他交换,则他的不高兴程度增加 2(即不高兴
程度为 3),依次类推。当要求某个小朋友第 k 次交换时,他的不高兴程度增加 k。

请问,要让所有小朋友按从低到高排队,他们的不高兴程度之和最小是多少。

如果有两个小朋友身高一样,则他们谁站在谁前面是没有关系的。

输入格式
输入的第一行包含一个整数 n,表示小朋友的个数。
第二行包含 n 个整数 H1,H2,…,Hn,分别表示每个小朋友的身高。

输出格式
输出一行,包含一个整数,表示小朋友的不高兴程度和的最小值。

数据范围
1≤n≤100000,0≤Hi≤1000000
输入样例:
3
3 2 1
输出样例:
9
样例解释
首先交换身高为3和2的小朋友,再交换身高为3和1的小朋友,再交换身高为2和1的小朋友,每个小朋友的不高兴程度都是3,总和为9。

示例代码一: 5/11

#include <cstdio>
#include <iostream>

using namespace std;

typedef long long LL;

const int N = 1e5+10;

int n;
int h[N];
LL res[N];
int cnt[N];

int main()
{
    scanf("%d", &n);
    for(int i = 0; i < n; ++i) scanf("%d", &h[i]);
    
    for(int i = 0; i < n - 1; ++i)
    {
        int count = 0;
        for(int j = 0; j < n - i - 1; ++j)
        {
            if(h[j] > h[j+1])
            {
                cnt[j]++, cnt[j+1]++;
                res[j] += cnt[j], res[j+1] += cnt[j+1];
                swap(h[j], h[j+1]);
                swap(cnt[j], cnt[j+1]);
                swap(res[j], res[j+1]);
                count++;
            }
        }
        if(count == 0) break;
    }
    
    LL ans = 0;
    for(int i = 0; i < n; ++i) ans += res[i];
    
    cout << ans << endl;
    
    return 0;
}

示例代码2: 11/11

#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

typedef long long LL;

const int N = 1e6+10;  //以高度为下标所以要一百万

int n;
int h[N], tr[N];  //h[i]:第i号人的高度  tr[i]:高度为i的人数
int sum[N];  //sum[i]:第i号人前后的人数

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

void add(int x, int v)
{
    for(int i = x; i < N; i += lowbit(i)) tr[i] += v;
}

int query(int x)
{
    int res = 0;
    for(int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

int main()
{
    scanf("%d", &n);
    for(int i = 0; i < n; ++i) scanf("%d", &h[i]), h[i]++;  //因为高度可能为0,而前缀和下标只能从1开始
    
    for(int i = 0; i < n; ++i)
    {
        sum[i] = query(N-1) - query(h[i]);  //找前面更大的
        add(h[i], 1);  //高度为h[i]的多了一人
    }
    
    memset(tr, 0, sizeof tr);  //清空tr数组
    for(int i = n - 1; i >= 0; --i)
    {
        sum[i] += query(h[i] -1);  //找后面更小的
        add(h[i], 1);
    }
    
    LL res = 0;
    for(int i = 0; i < n; ++i) res += (LL)sum[i] * (sum[i] + 1) / 2;  //移动了sum[i]次计算每个人的不高兴程度
    
    cout << res << endl;
    
    return 0;
}

二、仓库规划

标签:枚举

思想:这道题挺简单的,直接枚举就行了, n n n O ( M N 2 ) O(MN^2) O(MN2) ,数据范围也是允许暴力的,所以直接写。

题目描述:

西西艾弗岛上共有 n 个仓库,依次编号为 1∼n。

每个仓库均有一个 m 维向量的位置编码,用来表示仓库间的物流运转关系。

具体来说,每个仓库 i 均可能有一个上级仓库 j,满足:仓库 j 位置编码的每一维均大于仓库 i 位置编码的对应元素。

比如编码为 (1,1,1) 的仓库可以成为 (0,0,0) 的上级,但不能成为 (0,1,0) 的上级。

如果有多个仓库均满足该要求,则选取其中编号最小的仓库作为仓库 i 的上级仓库;如果没有仓库满足条件,则说明
仓库 i 是一个物流中心,没有上级仓库。

现给定 n 个仓库的位置编码,试计算每个仓库的上级仓库编号。

输入格式
输入共 n+1 行。
输入的第一行包含两个正整数 n 和 m,分别表示仓库个数和位置编码的维数。
接下来 n 行依次输入 n 个仓库的位置编码。其中第 i 行(1≤i≤n)包含 m 个整数,表示仓库 i 的位置编码。

输出格式
输出共 n 行。
第 i 行(1≤i≤n)输出一个整数,表示仓库 i 的上级仓库编号;如果仓库 i 没有上级,则第 i 行输出 0。

数据范围
50% 的测试数据满足 m=2;全部的测试数据满足 0<m≤100<n≤1000,且位置编码中的所有元素均为绝对值不大于 106
 的整数。

输入样例:
4 2
0 0
-1 -1
1 2
0 -1
输出样例:
3
1
0
3
样例解释
对于仓库 2(1,1) 来说,仓库 1(0,0) 和仓库 3(1,2) 均满足上级仓库的编码要求,因此选择编号较小的仓库 1
 作为其上级。

示例代码:

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n, m;
int w[N][10];

int main()
{
    scanf("%d%d", &n, &m);
    
    for(int i = 0; i < n; ++i)
    {
        for(int j = 0; j < m; ++j)
        {
            scanf("%d", &w[i][j]);
        }
    }
    
    for(int i = 0; i < n; ++i)
    {
        int res = 0;
        for(int j = 0; j < n; ++j)
        {
            bool flag = true;
            for(int k = 0; k < m; ++k)
            {
                if(w[i][k] >= w[j][k])
                {
                    flag = false;
                    break;
                }
            }
            if(flag)
            {
                res = j+1;
                break;
            }
        }
        printf("%d\n", res);
    }
    
    return 0;
}

三、股票买卖II

标签:贪心

思路:这道题就是贪心,如果明天涨了,那么今天就买。

题目描述:

给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

输入格式
第一行包含整数 N,表示数组长度。
第二行包含 N 个不大于 10000的正整数,表示完整的数组。

输出格式
输出一个整数,表示最大利润。

数据范围
1≤N≤105
输入样例16
7 1 5 3 6 4
输出样例17
输入样例25
1 2 3 4 5
输出样例24
输入样例35
7 6 4 3 1
输出样例30
样例解释
样例1:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 
= 5-1 = 4 。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获
得利润 = 6-3 = 3 。共得利润 4+3 = 7。

样例2:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润
 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,
 你必须在再次购买前出售掉之前的股票。

样例3:在这种情况下, 不进行任何交易, 所以最大利润为 0

示例代码:

#include <cstdio>
#include <iostream>

using namespace std;

typedef long long LL;

const int N = 1e5+10;

int n;
int w[N];

int main()
{
    scanf("%d", &n);
    for(int i = 0; i < n; ++i) scanf("%d", &w[i]);
    
    LL res = 0;
    for(int i = 1; i < n; ++i) 
    {
        if(w[i] > w[i-1]) res += w[i] - w[i-1];
    }
    
    printf("%lld\n", res);
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lijiachang030718

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值