题目描述
难度分:1600
输入n(1≤n≤105),长为n的数组a(0≤a[i]≤109),长为n的数组t(0≤t[i]≤109)。
下雪啦!0x3F
每天都要堆一个雪人,第i天0x3F
会堆一个体积为a[i]的雪人。
-
第i天的温度是t[i],体积为x的雪人在这天会融化掉min(t[i],x)的体积。
-
第i天新堆的雪人和之前堆的雪人都会融化。
对于每一天,输出这一天所有雪人融化掉的体积之和。
输入样例1
3
10 10 5
5 7 2
输出样例1
5 12 4
输入样例2
5
30 25 20 15 10
9 10 12 4 13
输出样例2
9 20 35 11 25
算法
贡献法
正着思考想了挺长时间,但是一直没想到“怎么在第i天高效动态维护[1,i]中体积小于t[i]的雪人数量和大于等于t[i]的雪人数量”。
所以就反向思考贡献了,对于第i天堆的雪人,我们思考它会在哪一段区间[i,j]融化。可以很容易发现,第i天雪人融化的日子[i,j]应该满足Σjk=it[k]≥a[i],由于t中都是非负数,所以先对t预处理出一个前缀和数组s。对于每个i,二分查找出满足s[j]−s[i−1]≥a[i]的最左j。
- 如果没有找到合法的j,说明后缀和Σnk=it[k]<a[i],也就是后面每天k∈[i,n]都会在第i个雪人上融化t[k]的体积,可以在区间[i,n]上加1(利用差分数组diff来操作),这是雪人i对第k天的贡献。
- 否则,当s[j]−s[i−1]>a[i]时,第i个雪人会在第k∈[i,j)天融化t[k]的体积,最后一天j,融化a[i]−(s[j−1]−s[i−1]),先把第j天的答案ans[j]加上a[i]−(s[j−1]−s[i−1])。当s[j]−s[i−1]=a[i]时,第i个雪人会在第k∈[i,j]天融化t[k]的体积。
最后遍历i∈[1,n],一边计算diff数组的前缀和,一边计算每天的答案。第i天的答案应该还要加上t[i]×Σik=1diff[k],其中Σik=1diff[k]表示融化t[i]体积的次数。
复杂度分析
时间复杂度
瓶颈在于计算第i个雪人融化的时间区间需要进行O(log2n)的二分查找,要计算n天,所以总的时间复杂度为O(nlog2n)。
空间复杂度
空间瓶颈在于t的前缀和数组s,以及辅助进行区间加的差分数组diff,空间都是线性的。因此,额外空间复杂度为O(n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int n, a[N], t[N];
LL s[N], diff[N], ans[N];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= n; i++) {
scanf("%d", &t[i]);
s[i] = s[i - 1] + t[i];
}
for(int i = 1; i <= n; i++) {
int l = i, r = n, j = 0;
while(l <= r) {
int mid = l + r >> 1;
if(s[mid] - s[i - 1] >= a[i]) {
j = mid;
r = mid - 1;
}else {
l = mid + 1;
}
}
// a[i]在日期[i,j]内都会融化
if(j) {
if(s[j] - s[i - 1] > a[i]) {
if(i <= j) {
ans[j] += a[i] - (s[j - 1] - s[i - 1]);
}
diff[i]++;
diff[j]--;
}else {
diff[i]++;
diff[j + 1]--;
}
}else {
diff[i]++;
diff[n + 1]++;
}
}
for(int i = 1; i <= n; i++) {
diff[i] += diff[i - 1];
ans[i] += diff[i]*t[i];
printf("%lld ", ans[i]);
}
return 0;
}