题目描述
难度分:901
输入n(1≤n≤2×105)和一个1~n的排列a。下标从1开始。
有n幢楼房,高度从左到右记录在a中。定义f(i)表示j的个数,满足i<j≤n且在[i+1,j−1]中没有比j高的楼房。
输出f(1),f(2),…,f(n)。
输入样例1
5
2 1 4 3 5
输出样例1
3 2 2 1 0
输入样例2
4
1 2 3 4
输出样例2
3 2 1 0
输入样例3
10
1 9 6 5 2 7 10 4 8 3
输出样例3
2 3 3 3 2 1 2 1 1 0
算法
动态规划+单调栈
乍一看感觉是LIS问题,f(i)应该是dp[i+1],其中dp[i+1]表示以a[i+1]开头的最长不递减子序列的长度。但是发现一个问题,如果数组为3 1 5 2 4 6
,要求a[1]的答案,dp[2]=4,可是a[2]开头的最长不递减子序列是1 2 4 6
,但是原数组中1和2之间有一个比2大的5,这是不合法的。因此,对于每个i,它的转移来源应该是j>i,且满足a[j]≥a[i]的最小j,这样才能保证(i,j)之间不会有>a[j]的数。
因此,倒序遍历数组a计算dp数组,用单调栈维护每个i的转移来源j,状态转移方程其实就是dp[i]=dp[j]+1。
复杂度分析
时间复杂度
倒序遍历一遍a数组就可以求得dp数组,在这个过程中还需要用单调栈维护信息,时间复杂度为O(n)。
空间复杂度
除了输入的a数组,空间消耗主要在于DP
数组dp,以及单调栈。因此,整个算法的额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, a[N], dp[N];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
dp[n + 1] = 0;
stack<int> stk;
for(int i = n; i >= 1; i--) {
while(stk.size() && a[stk.top()] < a[i]) {
stk.pop();
}
if(stk.size()) {
dp[i] = dp[stk.top()] + 1;
}else {
dp[i] = 1;
}
stk.push(i);
}
for(int i = 1; i <= n; i++) {
printf("%d ", dp[i + 1]);
}
return 0;
}