引言
关于这个树状数组和线段树还是挺重要的,主要题目不会直接问你求哪个区间的和什么的,题目往往是给出一个真实的例子,隐藏这些操作条件,需要让你自己去抽象出来这些操作,然后再来判断到底用什么数据结构和算法,所以刷题的好处就在于此,有些题自己根本就想不到还能这样做,所以只有不断地见大量的习题才行。
一、小朋友排队
标签:贪心、树状数组、排序
思路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≤10、0<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
输入样例1:
6
7 1 5 3 6 4
输出样例1:
7
输入样例2:
5
1 2 3 4 5
输出样例2:
4
输入样例3:
5
7 6 4 3 1
输出样例3:
0
样例解释
样例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;
}