【50.40%】【BZOJ 4553】[Tjoi2016&Heoi2016]序列
Submit: 371 Solved: 187
[Submit][Status][Discuss]
Description
佳媛姐姐过生日的时候,她的小伙伴从某宝上买了一个有趣的玩具送给他。玩具上有一个数列,数列中某些项的值
Input
输入的第一行有两个正整数n, m,分别表示序列的长度和变化的个数。接下来一行有n个数,表示这个数列原始的
Output
输出一个整数,表示对应的答案
Sample Input
1 2 3
1 2
2 3
2 1
3 4
Sample Output
【题解】
题的意思是说进行一次变化操作之后,变成新的序列,然后序列又变回原状,然后再进行下一个变换,变成新状态->又变回原状。。
然后在这几次变化之后形成的所有序列。找一个下标a1,a2..at,在这所有形成的序列中都都要满足a[a1]<=a[a2]<=...<=a[at].
求t的最大值。
设下标为i的数字最大会变成r[i],最小会变成l[i].然后原数字是v[i]
则第j个元素能接在第i个元素后面加长以第i个元素为结尾的最长上升序列的长度的条件是r[i] <= a[j]且a[i] <= l[j].前者是第i个元素发生变化,后者是第j个元素发生变化所要满足的情况。
还是提醒一句。进行一次操作过后。马上序列又变回原状。而且每次操作只会改变一个对应下标的数字。
则f[j] = max(f[i])+1;(i∈1..j-1;a[i] <= l[j]且r[i] <= a[j])
但是n=10W.
这种做法是n^2的。。
于是做一下改进。
我们在转移的时候实际上就是在找某些范围里面的点。
我们设点的坐标为(a[p],r[q]);(抽象)
那我们对第i个数字进行DP的时候
实际上就是要在某种数据结构中找坐标范围为(0..l[i],0..a[i])的点里面f值最大的。
转移完之后就获得了f[i]的值。
然后再把第i个点的f[i]加到这种数据结构里面就好了。
这种问题可以用kdtree来实现。
n太巨大。需要不时重建kdtree。不然插入操作会让kdtree退化。
经过试验每6000次重建一次效果最好。5000也能通过。
4000、10000这些周期则会T
具体看代码
【代码】
#include <cstdio> #include <algorithm> using namespace std; const int MAXN = 109000; int n, m,root,ans = 0,now; struct point { int l, r,v; }; struct node { int ma_x[2], mi_n[2], l, r, dot,f, max,d[2]; }; point shuju[MAXN]; node t[MAXN],op; int totn = 0,mi_n[2],ma_x[2]; void input_data() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%d", &shuju[i].v); shuju[i].l = shuju[i].r = shuju[i].v; } for (int i = 1; i <= m; i++) { int index, num; scanf("%d%d", &index, &num); shuju[index].l = min(shuju[index].l, num); shuju[index].r = max(shuju[index].r, num); } } void up_data(int rt) { int l = t[rt].l, r = t[rt].r; for (int i = 0; i <= 1; i++) { if (l) { t[rt].ma_x[i] = max(t[rt].ma_x[i], t[l].ma_x[i]); t[rt].mi_n[i] = min(t[rt].mi_n[i], t[l].mi_n[i]); if (t[l].max > t[rt].max) { t[rt].max = t[l].max; //t[rt].dot = t[l].dot; } } if (r) { t[rt].ma_x[i] = max(t[rt].ma_x[i], t[r].ma_x[i]); t[rt].mi_n[i] = min(t[rt].mi_n[i], t[r].mi_n[i]); if (t[r].max > t[rt].max) { t[rt].max = t[r].max; //t[rt].dot = t[r].dot; } } } } void insert(int &rt, int fx)//插入节点 { if (!rt) { rt = ++totn; t[rt] = op; t[rt].max = t[rt].f; t[rt].l = t[rt].r = 0; //t[rt].dot = rt; for (int i = 0; i <= 1; i++) t[rt].ma_x[i] = t[rt].mi_n[i] = t[rt].d[i]; return; } else { if (op.d[fx] < t[rt].d[fx]) insert(t[rt].l, 1 - fx); else insert(t[rt].r, 1 - fx); } up_data(rt); } int query(int rt) { if (!rt) return 0; if (mi_n[0] <= t[rt].mi_n[0] && t[rt].ma_x[0] <= ma_x[0] && mi_n[1] <= t[rt].mi_n[1] && t[rt].ma_x[1] <= ma_x[1]) return t[rt].max; //整棵子树里面的点都在所求范围内 if (t[rt].ma_x[0] < mi_n[0] || t[rt].mi_n[0] > ma_x[0] || t[rt].ma_x[1] < mi_n[1] || t[rt].mi_n[1] > ma_x[1])//都不在范围内 return 0; int big = 0; if (mi_n[0] <= t[rt].d[0] && t[rt].d[0] <= ma_x[0] && mi_n[1] <= t[rt].d[1] && t[rt].d[1] <= ma_x[1]) big = max(big, t[rt].f);//当前这个节点在范围内。 int l = t[rt].l, r = t[rt].r; if (l) big = max(big, query(l)); if (r) big = max(big, query(r)); return big; } bool cmp(node a, node b) { return a.d[now] < b.d[now]; } int rebuild(int begin, int end, int fx) { int m = (begin + end) >> 1; now = fx; nth_element(t + begin, t + m, t + end + 1, cmp); for (int i = 0; i <= 1; i++) t[m].ma_x[i] = t[m].mi_n[i] = t[m].d[i]; t[m].max = t[m].f; if (begin < m) t[m].l = rebuild(begin, m - 1, 1 - fx); else t[m].l = 0;//不能省,要重建的 if (m < end) t[m].r = rebuild(m + 1, end, 1 - fx); else t[m].r = 0; up_data(m); return m; } void get_ans() { //j < i r[j] <= v[i] //j < i v[j] <= l[i] op.d[0] = shuju[1].r; op.d[1] = shuju[1].v; op.f = 1; insert(root,0); ans = 1; for (int i = 2;i <= n;i++) { if ((i % 6000) == 0) { root = rebuild(1, totn,0); } mi_n[0] = 0; ma_x[0] = shuju[i].v; mi_n[1] = 0; ma_x[1] = shuju[i].l; int temp = query(root); op.d[0] = shuju[i].r; op.d[1] = shuju[i].v; op.f = 1; if (temp) { if (temp + 1 > ans) ans = temp + 1; op.f = temp + 1; } insert(root, 0); } } void output_ans() { printf("%d\n", ans); } int main() { //freopen("F:\\rush.txt", "r", stdin); input_data(); get_ans(); output_ans(); return 0; }