太nb了这题。佩服波兰人的脑洞。正解实在没学会,学了另外一种解法。不过似乎本质相同。
首先考虑设 f ( i , j ) f(i,j) f(i,j) 表示前 i i i 个积木里面保存了 j j j 个的方案数。
转移 f ( i , j ) = max { f ( i − 1 , j ) , f ( i − 1 , j − 1 ) + [ a i = j ] } f(i,j)=\max\{f(i-1,j),f(i-1,j-1)+[a_i=j] \} f(i,j)=max{f(i−1,j),f(i−1,j−1)+[ai=j]}。
这个东西维护非常困难。正解就是给出了一种转平面然后逆斜二测,对角线维护的方法。
我们考虑另一种本质相同的解法。转换状态。
设 f ( i ) f(i) f(i) 表示强制 a i a_i ai 在 i i i 上,最多有多少积木满足条件。
转移: f ( i ) = max j < i , a j < a i , a i − a j ≤ i − j { f ( j ) + 1 } f(i)=\max\limits_{j<i,a_j<a_i,a_i-a_j\le i-j} \{f(j)+1 \} f(i)=j<i,aj<ai,ai−aj≤i−jmax{f(j)+1}。
按照 i − a i i-a_i i−ai 排序,然后树状数组维护值域上的前缀最大值即可。时间复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn)。
// Problem: P6564 [POI2007] 堆积木KLO
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P6564
// Memory Limit: 128 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define fir first
#define sec second
using pii = std::pair<int, int>;
const int maxn = 1e5 + 5;
int n, c[maxn];
pii a[maxn];
int lowbit(int x) {
return x & -x;
}
void add(int x, int y) {
for(;x <= n;x += lowbit(x))
c[x] = std::max(c[x], y);
return ;
}
int query(int x) {
int ans = 0;
for(;x;x -= lowbit(x))
ans = std::max(ans, c[x]);
return ans;
}
int main() {
scanf("%d", &n);
for(int i = 1;i <= n;++ i)
scanf("%d", &a[i].sec), a[i].fir = i;
std::sort(a + 1, a + 1 + n, [&](const pii& p, const pii& q) {
return (p.fir - p.sec == q.fir - q.sec) ? (p.fir < q.fir) : (p.fir - p.sec < q.fir - q.sec);
});
for(int i = 1;i <= n;++ i) {
if(a[i].sec > a[i].fir)
continue ;
add(a[i].sec, query(a[i].sec - 1) + 1);
}
printf("%d\n", query(n));
return 0;
}