题目链接
感谢emofunx提供解题思路和耐心答疑!
题目大意:
给你一个高度为n的数字金字塔,求它的所有高度为k的子金字塔的最大值的总和。
(
1
≤
k
≤
n
≤
3000
)
(1\le k \le n \le 3000)
(1≤k≤n≤3000)
样例输入:
4 2
3
1 2
4 2 1
6 1 4 2
样例输出:
23
解题思路:
对于一个可以移动的固定大小区间内的区间,我们可以想到用滑窗去解决,但是这是二维的,而且还是三角形,咋滑啊?
这时候emofunx说道:其实这类问题的处理都差不多,就是可能麻烦点。
注意到一个大的三角形,是可以由多个小的三角形组成的,这时候我们可以想倍增:预处理出所有高为2^i的三角形的最值。那么
2
i
2^i
2i高度的三角形如何推出
2
i
+
1
2^{i+1}
2i+1的三角形呢,看图:
如图,可以用三个
2
i
2^i
2i高度的三角形拼出1个中空的
2
i
+
1
2^{i+1}
2i+1的三角形,因为有空洞,我们不能直接递推。但是我们可以发现:
假设当前三角形左下角为
(
x
,
y
)
(x,y)
(x,y)红色这部分的面积可以被左下角坐标为
(
x
,
y
)
到
(
x
+
2
i
,
y
)
(x,y)到(x+2^i, y)
(x,y)到(x+2i,y),高度为
2
i
2^i
2i的这
2
i
+
1
2^i+1
2i+1个小三角形完全覆盖。
所以我们可以用滑窗法维护一段区间长度固定的数字的最值来转移维护出倍增数组。最后也可以用滑窗来利用倍增数组得到每个大小为k的子金字塔的最大值,把它们都加到答案里就完事儿了。
ac代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 3e3 + 50;
int f[2][maxn][maxn], a[maxn][maxn];
int n, k;
int q[maxn], head, tail;
int main()
{
cin>>n>>k;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= i; ++j) scanf("%d", &a[i][j]), f[0][i][j] = a[i][j];
} ll ans = 0;
if(k == 1){// 特判
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= i; ++j) ans += a[i][j];
}cout<<ans<<endl; return 0;
}
int cur = 0;
for(int t = 1; ; ++t){
cur ^= 1;//滚动倍增数组
int len, l;//需要获取的三角形大小,滑窗的大小
if((1<<t) < k) len = (1<<t), l = len>>1;
else len = k, l = k - (1<<(t-1));
l++;
for(int i = len; i <= n; ++i){
head = tail = 0;
int o = 1;
for(int j = 1; j + len - 1 <= i; ++j){
while(o <= j + l - 1){//滑窗获取区间最值
while(tail > head && f[cur^1][i][q[tail-1]] <= f[cur^1][i][o]) tail--;
q[tail++] = o++;
}
f[cur][i][j] = max(f[cur^1][i][q[head]], f[cur^1][i-l+1][j]);
if(len == k) ans = ans + (ll) f[cur][i][j];
if(q[head] == j) head++;
}
}
if(len == k) break;
}
cout<<ans<<endl;
}
/*
4 3
3
1 2
2 1 3
4 1 1 1
*/