[Offer收割]编程练习赛34
A 共同富裕
时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
给定一个数组A1, A2, ... AN,每次操作可以从中选定一个元素Ai,把除了Ai之外的所有元素都加1。
问最少几次操作可以实现“共同富裕”,即数组中所有元素都相等。
例如对于[1, 1, 1, 2, 3]经过3步:[1, 1, 1, 2, 3] -> [2, 2, 2, 3, 3] -> [3, 3, 3, 3, 4] -> [4, 4, 4, 4, 4]。
输入
第一行包含一个整数N。(1 ≤ N ≤ 100000)
以下N行包含N个整数A1, A2, ... AN。 (1 ≤ Ai ≤ 100000)
输出
最小的操作数
样例输入
5
1
1
1
2
3
样例输出
3
思路一
每轮选择n-1个加一相当于每轮选择一个减一,所以答案就是将所有值见到最小值所需次数。复杂度O(n)
。典型的正难则反
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int a[N];
int main() {
int n;
cin >> n;
ll sum = 0;
ll temp = 0;
ll mn = N;
for (int i = 0; i < n; i++) {
cin >> temp;
sum += temp;
mn = min(temp, mn);
}
cout << sum - mn * n << "\n";
return 0;
}
思路二
假设加了m轮,则最后的和为sum(a) + m * (n-1)。所以有:
{sum(a) + m * (n-1)} % n == 0
min(a) + m >= {sum(a) + m * (n-1)} / n >= max(a)
注意溢出
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int a[N];
int main() {
int n;
cin >> n;
ll sum = 0;
for (int i = 0; i < n; i++) {
cin >> a[i];
sum += a[i];
}
sort(a, a + n);
long long remain = sum % n;
long long avg = sum / n + remain;
while (a[0] + remain < avg) {
avg += n - 1;
remain += n;
}
cout << remain << "\n";
return 0;
}
B 股票价格3
时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
小Hi最近在关注股票,为了计算股票可能的盈利,他获取了一只股票最近N天的价格A1~AN。
小Hi想知道,对于第i天的股票价格Ai,几天之后股价会第一次超过Ai。
假设A=[69, 73, 68, 81, 82],则对于A1=69,1天之后的股票价格就超过了A1;对于A2=73,则是2天之后股票价格才超过A2。
输入
第一行包含一个整数N。
以下N行每行包含一个整数Ai。
对于50%的数据,1 ≤ N ≤ 1000
对于100%的数据,1 ≤ N ≤ 100000, 1 ≤ Ai ≤ 1000000
输出
输出N行,其中第i行代表对于第i天的股票价格Ai,几天之后股价会第一次超过Ai。
如果Ai+1~AN之内没有超过Ai,输出-1。
样例输入
5
69
73
68
81
82
样例输出
1
2
1
1
-1
思路一
对pair<value, index>
排序,用一个set表示可以用的index。遍历排序后的数组二分找出满足的最小index即可,每次遍历之后将该index从set中删除,即不可用。
复杂度 O(nlgn)
注意corner case: 数组中有一样的值
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
typedef pair<int, int> p;
p a[N];
int ans[N];
int n;
int main() {
set<int> s;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i].first;
a[i].second = i;
s.insert(i);
}
sort(a, a + n);
for (int i = 0; i < n; i++) {
int j = i;
while (j < n && a[j].first == a[i].first) j++;
if (j > i + 1) {
for (int k = i; k < j && k < n && s.count(a[k].second); k++) {
int cur = a[k].second;
s.erase(cur);
}
}
int cur = a[i].second;
auto it = s.upper_bound(cur);
if (it == s.end()) {
ans[cur] = -1;
} else {
ans[cur] = *it - cur;
}
s.erase(cur);
}
for (int i = 0; i < n; i++) {
cout << ans[i] << "\n";
}
return 0;
}
思路二 线段树或树状数组
倒序处理数组,维护a[i+1] ~ a[n-1] 的线段树,可快速找出满足最小的j使得a[j] > a[i] 。
复杂度O(nlgn)
单调栈
从前往后扫,维护一个递减的单调栈:对于当前元素a[i] 以:
*若当前元素小于等于栈顶的,则直接入栈
- 否则, 将栈内的元素出栈直至栈顶大于a[i],对于出栈的元素对应的结果就是i
复杂度O(n)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5;
int A[maxn];
int ans[maxn];
int main() {
int n;
cin>>n;
for (int i = 0; i < n; ++i)
cin>>A[i];
stack<pair<int, int> > s;
for (int i = 0; i < n; ++i) {
if (s.empty() || s.top().first >= A[i])
s.push({A[i], i});
else {
while (!s.empty() && s.top().first < A[i]) {
ans[s.top().second] = i - s.top().second;
s.pop();
}
s.push({A[i], i});
}
}
while (!s.empty()) {
ans[s.top().second] = -1;
s.pop();
}
for (int i = 0; i < n; ++i)
printf("%d\n", ans[i]);
return 0;
}