线段树+LIS

https://vjudge.net/contest/237022#problem/C

题意:有一组数1~n,按顺序给出每个数需要插入的位置(即第i数代表数字i要插入当前序列的第ai个),组合成新的序列,问每插入一个新的数,当前序列的最长上升子序列为多长?

解法:首先用线段树插空法将数列的最终位置还原出来,得到1~n的对应位置。而对于每次操作,当前序列的最长上升子序列长度等于1~n对应位置所构成的序列的最长上升子序列长度(可手算推导)。由于操作顺序从1~n,所以第i次操作只要对前i个数(即1~i)对应的位置组成的序列求LIS即可。

线段树插空法:从最后一个数开始,逆推每个数的对应位置,因为放置最后一个数时,其位置已经可以确定。用数组s记录每个操作数的插入位置,则逆推过程中,s[i]表示第i个前面需要留有s[i]个空格。线段树数中的a[i].l,a[i].r,a[i].n分别代表第i个结点的左右边界以及对应区间的空格数。记得初始化,即l==r时,该区间空格数为1。

LIS计算:用数组dp[]记录当前序列的最长上升子序列,len记录当前的最长长度,ans[i]记录数字i对应的位置。利用二分的方法,从1~n循环,每次寻找dp数组中第一个大于等于ans[i]的数,用k记录该数在dp数组中的位置(实质上k为ans[i]在满足序列上升的条件下放到dp数组中的对应位置),更新len(如果ans[i]比dp中的数都大,len=k;否则len保持不变),输出len,并用ans[i]代替dp[k](可手动模拟推导)。

  1 //#include<bits/stdc++.h>
  2 #include <iostream>
  3 #include <algorithm>
  4 #include <cstdio>
  5 #include <cstring>
  6 #include <string>
  7 #include <cmath>
  8 #include <cstdlib>
  9 #include <queue>
 10 #include <stack>
 11 #include <map>
 12 #include <vector>
 13 #include <set>
 14 #include <bitset>
 15 #include <iomanip>
 16 #define ms(a, b) memset(a, b, sizeof(a));
 17 using namespace std;
 18 typedef long long LL;
 19 typedef pair<int, int> pii;
 20 const int INF = 0x3f3f3f3f;
 21 const int maxn = 1e5 + 10;
 22 const int MAXN = 2e4 + 10;
 23 const double eps = 1e-8;
 24 const int mod = 1e9 + 7;
 25 int n, len;
 26 int s[maxn],ans[maxn], dp[maxn];
 27 
 28 struct node {
 29     int l, r, n;
 30 }a[maxn<<2];
 31 
 32 void build(int l, int r, int i) { //初始化各个结点的空格数
 33     a[i].l = l;
 34     a[i].r = r;
 35     if(l == r) {
 36         a[i].n = 1;
 37         return;
 38     }
 39     int mid = (l + r) >> 1;
 40     build(l, mid, i*2);
 41     build(mid+1, r, i*2+1);
 42     a[i].n = a[i*2].n + a[i*2+1].n;
 43     return;
 44 }
 45 
 46 void update(int i, int x, int m) { //i为结点编号,数字m在当前序列的第x个空格
 47     if(a[i].l == a[i].r) {
 48         a[i].n = 0;
 49         ans[m] = a[i].l;
 50         return;
 51     }
 52     a[i].n--;
 53     if(x <= a[2*i].n) update(2*i, x, m);
 54     else update(2*i+1, x-a[2*i].n, m);
 55     return;
 56 }
 57 
 58 int solve(int k) {
 59     int l = 1, r = len;
 60     while(l <= r) {
 61         int mid = (l + r) >> 1;
 62         if(dp[mid] > k) r = mid - 1;
 63         else l = mid + 1;
 64     }
 65     return l; //l或者r+1都可以
 66 }
 67 
 68 
 69 int main() {
 70 #ifdef local
 71     freopen("case.in", "r", stdin);
 72 //    freopen("case.out", "w", stdout);
 73 #endif
 74 //    ios::sync_with_stdio(false);
 75 //    cin.tie(0);
 76     int T;
 77     scanf("%d", &T);
 78     int kase = 0;
 79     while(T--) {
 80         scanf("%d", &n);
 81         build(1, n, 1);
 82         for(int i = 1; i <= n; i++) {
 83             scanf("%d", &s[i]);
 84             dp[i] = 0;
 85         }
 86         for(int i = n; i > 0; i--) {
 87             update(1, s[i]+1, i);
 88         }
 89 //        for(int i = 1; i <= n; i++) cout << ans[i] << " ";
 90         len = 1;
 91         dp[1] = ans[1];
 92         printf("Case #%d:\n", ++kase);
 93         printf("%d\n", len);
 94         for(int i = 2; i <= n; i++) {
 95             int k = solve(ans[i]);
 96             len = max(k, len);
 97             dp[k] = ans[i];
 98             printf("%d\n", len);
 99         }
100         printf("\n");
101     }
102 //    solve();
103     return 0;
104 }

 

posted @ 2018-08-02 22:04  Sissi_hss  阅读(481)  评论(0编辑  收藏  举报