2022.2.6解题报告
1.平衡数组
题目描述:
https://www.acwing.com/problem/content/3781/
思路:
假设一个长度为n的数组:
1,2,3,4,… n - 1, n
再假设它最后都变成相等的值为k:
k,k,k,k,… k,k
那么每一个数变成k所要加的值就分别为:
k - 1,k - 2,k - 3,k - 4 … k - n + 1,k - n
再假设一共经过m次操作,那么每个数实际加上的数值应为:
m*(m + 1) / 2 - 不给该数加值的操作的序号
那么,只需要满足k = m*(m + 1) / 2,“不给该数加值的操作的序号” = 该数本身的序号即可满足条件
又因为这些序号(1~m)每个必须且只能出现一次,所以只需再让m = n即可
所以总结为:
操作次数为n次,每一次操作使得本次操作序号等于不选的数字的序号即可满足条件
(其实手动模拟一下立马就懂了)
例如:
1 2 3 => 1 3 4 => 3 3 6 => 6 6 6
1 2 3 4 => 1 3 4 5 => 3 3 6 7 => 6 6 6 10 => 10 10 10 10
代码:
#include <iostream>
using namespace std;
const int N = 110;
int n, a[N];
int main() {
int T;
scanf("%d", &T);
while (T -- ) {
scanf("%d", &n);
printf("%d\n", n); //输出操作次数m
for (int i = 1 ; i <= n ; i ++ )
printf("%d ", i);
printf("\n");
}
return 0;
}
2.相等的和
题目描述:
https://www.acwing.com/problem/content/3782/
思路:
由于只需要输出任意一个满足题意的方案,且操作只能进行一次,所以我们大可以直接枚举
对于一个序列:
a1,a2,a3 … an
它在删去一个元素后的所有元素之和最多只有n种情况,把它们都记录下来,存储在同一行中:
sum1,sum2,sum3 … sumn
所以我们只需要用哈希表来查找不在同一行的数值相等的sum值即可。
代码:
#include <iostream>
#include <unordered_map>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 200010;
int w[N], k;
int main() {
scanf("%d", &k);
unordered_map<int, PII> S; //建立哈希表
for (int i = 1 ; i <= k ; i ++ ) {
int n, sum = 0;
scanf("%d", &n);
for (int j = 1 ; j <= n ; j ++ ) {
scanf("%d", w + j);
sum += w[j]; //算出不删除元素时的元素总和
}
for (int j = 1 ; j <= n ; j ++ ) {
int t = sum - w[j]; //算出不同的sum值
if (S.count(t) && S[t].x != i) {
//如果哈希表中有相等且不同行的元素,直接输出
puts("YES");
printf("%d %d\n", S[t].x, S[t].y);
printf("%d %d\n", i, j);
return 0;
}
S[t] = {i, j};
//将新的一种sum及其对应的横纵坐标存入哈希表
}
}
puts("NO");
return 0;
}
3.构造数组
题目描述:
https://www.acwing.com/problem/content/3783/
思路:
由题意可知,构造出来的数组一定是单峰的,即只有一下三种情况:
a1 > a2 > a3 > … > an
a1 < a2 < a3 < … < an
a1 < a2 < … < ak > … > an
所以我们可以定义两个数组l[i]和r[i],它们的定义为:
l[i]:在1~i中 所能构造的 总和最大的 单调递增序列 的总和
r[i]:在i~n中 所能构造的 总和最大的 单调递减序列 的总和
那么上述的三种情况的答案就分别为:
r[1]
l[n]
l[k] + r[k + 1](或l[k] + r[k] - a[k])
那么现在再来看l[i]和r[i]该怎么求:
由于l[i]和r[i]是对称的,所以我们只需要研究l[i]怎么求即可。
首先,我们先求一个理论最大值。
从第i个数开始往前看,显然a[i]的最大值为m[i],那么a[i - 1]的最大值就是min(a[i],m[i - 1])
依此类推。
显然,这样子取数构造数组可以使得数组中的所有数字取到它的最大值且满足条件,因此理论最大值就是实际最大值。
但由于这样每一个数去求的方法时间复杂度为O(n^2)会超时,所以我们还需要研究一下这个序列的性质来优化。
对于a[i],它的最大值一定是m[i];
而对于第i - 1个数,如果m[i - 1] > m[i],由上述计算方式可得,a[i - 1]的最大值也是m[i]。
依此类推,直到第一个m < m[i]。
所以我们只需要从i开始向前找,找到中第一个小于m[i]的m[j],再用l[j]加上j~i这一段中的所有m[i]的值即可。
这可以用单调栈来解决。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 500010;
int n;
int a[N];
LL l[N], r[N]; //总和有可能爆int
int stk[N];
int main() {
scanf("%d", &n);
for (int i = 1 ; i <= n ; i ++ )
scanf("%d", a + i);
int tt = 0;
for (int i = 1 ; i <= n ; i ++ ) {
while (tt && a[stk[tt]] >= a[i])
tt -- ;
l[i] = l[stk[tt]] + (LL)(i - stk[tt]) * a[i];
stk[ ++ tt] = i;
}
tt = 0;
stk[0] = n + 1;
for (int i = n ; i ; i -- ) {
while (tt && a[stk[tt]] >= a[i])
tt -- ;
r[i] = r[stk[tt]] + (LL)(stk[tt] - i) * a[i];
stk[ ++ tt] = i;
}
LL res = 0, k = 0;
for (int i = 1 ; i <= n ; i ++ ) {
LL t = l[i] + r[i + 1];
if (t > res)
res = t, k = i;
}
for (int i = k - 1 ; i ; i -- )
a[i] = min(a[i], a[i + 1]);
for (int i = k + 2 ; i <= n ; i ++ )
a[i] = min(a[i], a[i - 1]);
for (int i = 1 ; i <= n ; i ++ )
printf("%d ", a[i]);
return 0;
}