题目链接:hdu 4521
本是 dp 的变形,却能用线段树,感觉好强大。
由于 n 有 10^5,用普通的 dp,算法时间复杂度为 O(n2),肯定会超时。所以用线段树进行优化。线段树维护的是区间内包含某点的最大满足条件的长度,叶子节点以该元素结尾,最长长度。至于相邻两项隔 d 个位置,求 dp[i] 时,我们只把 dp[i - d - 1] 更新至线段树中,然后在这颗线段树中找最大的个数。
具体来说,就是把序列 S 的值 Ai 作为线段树叶子下标,以 Ai 结尾的 LIS 长度(即经典算法里的 dp[i])作为叶子结点的值,然后对每个 Ai,查询 0 ~ Ai - 1 的最大的 LIS 长度(也就是 dp[]),这个用线段树实现可以很快地得到结果;至于更新时,不能每遍历到一个就直接更新,因为这样子的话 A[i - d] ~ A[i - 1] 会被加入到线段树中,它们对 A[i] 无任何作用,却会对线段树的查询结果有干扰,因此我们在对每一个 Ai 进行查询(即计算出对应的 dp[i])前,就只把 A[i - d - 1] 即 dp[i - d - 1] 加入到线段树中即可,这个操作是很关键的。理清思路后,就是线段树的单点更新、区间查询了。
因为我写线段树习惯从 1 开始,所以对所有元素都 +1 了。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 #define root int rt, int l, int r 6 #define lson rt << 1, l, mid 7 #define rson rt << 1 | 1, mid + 1, r 8 #define makemid int mid = l + r >> 1 9 const int N = 100005; 10 11 int len[N << 2]; 12 13 void build(root) { 14 len[rt] = 0; 15 if(l == r) return ; 16 makemid; 17 build(lson); 18 build(rson); 19 } 20 21 inline void pushup(int rt) { 22 len[rt] = max(len[rt << 1], len[rt << 1 | 1]); 23 } 24 25 int pos, val; 26 void update(root) { 27 if(l == r) { 28 len[rt] = max(len[rt], val); 29 return ; 30 } 31 makemid; 32 if(pos <= mid) update(lson); 33 else update(rson); 34 pushup(rt); 35 } 36 37 int ql,qr; 38 int query(root) { 39 if(ql <= l && r <= qr) return len[rt]; 40 makemid; 41 int res = 0; 42 if(ql <= mid) res = max(res, query(lson)); 43 if(qr > mid) res = max(res, query(rson)); 44 return res; 45 } 46 47 int dp[N], num[N]; 48 49 int main() { 50 int n,d; 51 while(~scanf("%d%d",&n,&d)) { 52 int mm = 0, ans = 0; 53 for(int i = 1; i <= n; ++i) { 54 scanf("%d",num + i); 55 ++num[i]; 56 mm = max(mm, num[i]); 57 } 58 build(1,1,mm); 59 for(int i = 1; i <= n; ++i) { 60 if(i - d - 1 >= 1) { //这是关键,只把 i-d-1 前面的更新至线段树中 61 pos = num[i - d - 1]; 62 val = dp[i - d - 1]; 63 update(1,1,mm); 64 } 65 if(num[i] == 1) dp[i] = 1; 66 else { 67 ql = 1; 68 qr = num[i] - 1; 69 dp[i] = query(1,1,mm) + 1; 70 } 71 ans = max(ans, dp[i]); 72 } 73 printf("%d\n",ans); 74 } 75 return 0; 76 }
时间: 2024-10-29 19:09:39