Codeforces 650D. Zip-line (动态LIS) (可持久化线段树 或 离线+树状数组)
2016-03-11 17:38
555 查看
题意: 给定一个长度为n的数列,和m个询问,每个询问的格式是:将原数组的第a个数改成b之后,数组的最长上升子序列(LIS)的长度。
做法:可持久化线段树 或 离线+树状数组
令 h[ ] 为原数组,LIS_L[ i ] 表示以 i 结尾的LIS长度,LIS_R[ i ] 表示以 i 开头的LIS长度。这俩数组用普通LIS算法O(nlog2(n))求出。
然后来分析一下询问怎么处理:
在 h[a]=b之后,整个数列的LIS分两部分,一是包含b的LIS和不包含b的LIS。
包含b的LIS:
1. 在 i < a 且 h[ i ] < b 的所有LIS_L[ i ]中求最大值,这是可以接在b之前的最大LIS长度。
2. 在 i > a 且 h[ i ] > b 的所有LIS_R[ i ]中求最大值,这是可以接在b之后的最大LIS长度。
所以包含b的LIS长度就是上面两个长度之和再加上一。
不包含b的LIS:
假设原数组的LIS长度为k,挖掉下标为a的这个数之后,有两种情况。
1. 所有的LIS都包括h[a](这里是指原数组的h[a]),这时新LIS长度为 k - 1.
2.至少有一个LIS不包括h[a],这时LIS长度不受影响,仍为k。
对上面两个值求最大值,就是询问的答案。
所以问题就只剩下了两个:
问题一:对于包含b的LIS,如何快速求出那两个值。
问题二:对于不包含b的LIS,怎么判断是否所有的LIS都经过h[a]。
问题一: 可持久化线段树 或者 离线+树状数组
要求的值:在 i < a 且 h[ i ] < b 的所有LIS_L[ i ]中求最大值。
可持久化线段树:
注意到这里有两个限制条件,h[ i ] < b 这个条件,可以将所有数值离散化,然后将所有LIS_L的值都加入进去,
不过,每个LIS_L[ i ]加入的横坐标是h[i]对应的排名,这样通过线段树的区间查询,就可以找到h[ i ] < b 的所有LIS_L值。
问题在于第一个条件,要求 i < a ,也就是说,随便给一个i,要知道前 i 个数形成的上述线段树,明显用可持久化线段树。
直接建n棵树,对每个 i 值建立前 i 个值形成的线段树。空间复杂度O(nlog2(n)),时间复杂度O(nlog2(n))。
问题解决,R那边对称着写一写就好了。
离线+树状数组:
上面的解法中,h[ i ] < b这条件是靠线段树区间询问来解决的,而 i < a 这个条件是利用了可持久化数据结构。
另一种解决 i < a 这个条件的做法就是将询问离线,也就是说,在每个 i 上,记录有多少个询问需要前 i 个LIS_L组成的线段树。
在依次将元素加入线段树的同时,遇到可以计算的询问,就计算,这样的话,只需要普通的线段树来维护最大值就行了。
然后维护最大值用树状数组会更快,所以就直接离线+树状数组解决。
从左到右扫描一遍,再从右往左扫描一遍,就计算完毕。
问题二:
首先一个结论:如果一个数h[a]存在于多个LIS中,那么它在多个LIS中的排名是一样的,比如它在某个LIS中是第二个数,那么它在所有LIS中都是第二个数。
用数组pos[ i ] 来存每个数在LIS中的位置,0表示不在LIS中。
如果LIS_L[ i ] + LIS_R[ i ] = k + 1 那么,这个数就在LIS中,并且位置是 LIS_L[ i ]。
否则,不在LIS中。
然后用Count数组,对所有的位置进行统计,Count[ j ] 表示有多少个数可以担任位置 j 。
这样处理完之后,对于i来说,只要 Count[ pos[ i ] ] 等于 1,就表示所有LIS都经过这个数,所以拿走之后LIS长度为 k - 1.
否则,LIS长度为 k 。
两个问题都解决了,这题也就做出来了。
终于又碰到一题可以用可持久化线段树了,真是不容易。虽然这题每段代码都是左写一遍右写一遍,但是还是写的很爽。
数组开小了一点点WA了好几次,只小了一点点,所以越界了一点点但是没有访问到非法内存,所以仍然报的是WA而不是RE。
可持久化线段树(1933ms 201968KB):
/* 1933 ms 201968 KB */ #include <iostream> #include <cstdio> #include <cmath> #include <cstring> #include <algorithm> #define out(i) <<#i<<"="<<(i)<<" " #define OUT1(a1) cout out(a1) <<endl #define OUT2(a1,a2) cout out(a1) out(a2) <<endl #define OUT3(a1,a2,a3) cout out(a1) out(a2) out(a3)<<endl #define maxn 400007 using namespace std; //离散化 int Rank[maxn],Rn; void SetRank(int n){ sort(Rank+1,Rank+n+1); Rn = 1; for(int i=2;i<=n;++i) if(Rank[i]^Rank[i-1]) Rank[++Rn]=Rank[i]; } int GetRank(int x){ int L=1,R=Rn,M;//[L,R] first >= x while(L^R){ M = (L+R)>>1; if(Rank[M] < x) L = M + 1; else R = M; } return L; } int GetRank_R(int x){ int L=1,R=Rn,M;//[L,R] first > x while(L^R){ M = (L+R)>>1; if(Rank[M] <= x) L = M + 1; else R = M; } return L; } //题目数据 int n,m,a,b,k; int h[maxn]; //LIS部分 int LIS_L[maxn],LIS_R[maxn],Len[maxn],Ln; int Test_L(int v){ int L=0,R=Ln+1,M;//[L,R) last < v while(L + 1 < R){ M = (L + R) >> 1; if(Len[M] < v) L = M; else R = M; } return L; } int Test_R(int v){ int L=0,R=Ln+1,M;//[L,R) last > v while(L + 1 < R){ M = (L + R) >> 1; if(Len[M] > v) L = M; else R = M; } return L; } void LIS(){ Len[0]=Ln=0; for(int i=1;i<=n;++i){ LIS_L[i] = Test_L(h[i]) + 1; if(Ln < LIS_L[i]) Len[++Ln] = h[i]; else Len[LIS_L[i]]=min(Len[LIS_L[i]],h[i]); } k = Ln;//记录最长LIS Len[0]=1000000001;Ln=0; for(int i=n;i>=1;--i){ LIS_R[i] = Test_R(h[i]) + 1; if(Ln < LIS_R[i]) Len[++Ln] = h[i]; else Len[LIS_R[i]]=max(Len[LIS_R[i]],h[i]); } } //主席树部分 int LPS_L[maxn*20],LPS_R[maxn*20],LPS_V[maxn*20],LPS_T[maxn],LPS_TP; void LPS_Add(int &rt,int l,int r,int x,int v){ ++LPS_TP; LPS_L[LPS_TP]=LPS_L[rt]; LPS_R[LPS_TP]=LPS_R[rt]; LPS_V[LPS_TP]=max(LPS_V[rt],v); rt = LPS_TP; if(l==r) return; int m = (l + r) >> 1; if(x <= m) LPS_Add(LPS_L[rt],l,m,x,v); else LPS_Add(LPS_R[rt],m+1,r,x,v); } void LPS_Build(){ LPS_L[0]=LPS_R[0]=LPS_V[0]=LPS_T[0]=LPS_TP=0; for(int i=1;i<=n;++i){ LPS_Add(LPS_T[i]=LPS_T[i-1],1,Rn,GetRank(h[i]),LIS_L[i]); } } int LPS_Query(int rt,int l,int r,int x){ if(l == r) return LPS_V[rt]; int m = (l + r) >> 1; if(x <= m) return LPS_Query(LPS_L[rt],l,m,x); else return max(LPS_V[LPS_L[rt]],LPS_Query(LPS_R[rt],m+1,r,x)); } int RPS_L[maxn*20],RPS_R[maxn*20],RPS_V[maxn*20],RPS_T[maxn],RPS_TP; void RPS_Add(int &rt,int l,int r,int x,int v){ ++RPS_TP; RPS_L[RPS_TP]=RPS_L[rt]; RPS_R[RPS_TP]=RPS_R[rt]; RPS_V[RPS_TP]=max(RPS_V[rt],v); rt = RPS_TP; if(l==r) return; int m = (l + r) >> 1; if(x <= m) RPS_Add(RPS_L[rt],l,m,x,v); else RPS_Add(RPS_R[rt],m+1,r,x,v); } void RPS_Build(){ RPS_L[n+1]=RPS_R[n+1]=RPS_V[n+1]=RPS_T[n+1]=RPS_TP=0; for(int i=n;i>=1;--i){ RPS_Add(RPS_T[i]=RPS_T[i+1],1,Rn,GetRank(h[i]),LIS_R[i]); } } int RPS_Query(int rt,int l,int r,int x){ if(l == r) return RPS_V[rt]; int m = (l + r) >> 1; if(x <= m) return max(RPS_Query(RPS_L[rt],l,m,x),RPS_V[RPS_R[rt]]); else return RPS_Query(RPS_R[rt],m+1,r,x); } int Count[maxn];//每种位置的数的数量 int pos[maxn];//第i个数在LIS中的位置,0表示不在LIS中 int main(void) { while(~scanf("%d%d",&n,&m)){ for(int i=1;i<=n;++i) scanf("%d",&h[i]),Rank[i]=h[i]; //离散化 Rank[n+1]=0;Rank[n+2]=1000000001; SetRank(n+2); //建立LIS数组 LIS(); //建主席树 LPS_Build(); RPS_Build(); memset(Count,0,sizeof(Count)); for(int i=1;i<=n;++i){ int L = LIS_L[i],R = LIS_R[i]; if(L+R==k+1){ Count[pos[i]=L]++; } else pos[i]=0; } for(int i=1;i<=m;++i){ scanf("%d%d",&a,&b); int _L = a - 1, _R = a + 1; int ANS1=1+LPS_Query(LPS_T[_L],1,Rn,GetRank(b)-1)+RPS_Query(RPS_T[_R],1,Rn,GetRank_R(b)); int ANS2=Count[pos[a]]^1?k:k-1; printf("%d\n",max(ANS1,ANS2)); } } return 0; }
离线+树状数组(873 ms 39200 KB):
/* 873 ms<span style="white-space:pre"> </span>39200 KB */ #include <iostream> #include <cstdio> #include <cmath> #include <cstring> #include <algorithm> #include <list> #define out(i) <<#i<<"="<<(i)<<" " #define OUT1(a1) cout out(a1) <<endl #define OUT2(a1,a2) cout out(a1) out(a2) <<endl #define OUT3(a1,a2,a3) cout out(a1) out(a2) out(a3)<<endl #define maxn 400007 using namespace std; struct Node{ int id,v; Node(){} Node(int id,int v):id(id),v(v){} }; list<Node> List[maxn]; //离散化 int Rank[maxn<<1],Rn; void SetRank(int n){ sort(Rank+1,Rank+n+1); Rn = 1; for(int i=2;i<=n;++i) if(Rank[i]^Rank[i-1]) Rank[++Rn]=Rank[i]; } int GetRank(int x){ int L=1,R=Rn,M;//[L,R] first >= x while(L^R){ M = (L+R)>>1; if(Rank[M] < x) L = M + 1; else R = M; } return L; } //题目数据 int n,m,a[maxn],b[maxn],k; int h[maxn]; //LIS部分 int LIS_L[maxn],LIS_R[maxn],Len[maxn],Ln; int Test_L(int v){ int L=0,R=Ln+1,M;//[L,R) last < v while(L + 1 < R){ M = (L + R) >> 1; if(Len[M] < v) L = M; else R = M; } return L; } int Test_R(int v){ int L=0,R=Ln+1,M;//[L,R) last > v while(L + 1 < R){ M = (L + R) >> 1; if(Len[M] > v) L = M; else R = M; } return L; } void LIS(){ Len[0]=Ln=0; for(int i=1;i<=n;++i){ LIS_L[i] = Test_L(h[i]) + 1; if(Ln < LIS_L[i]) Len[++Ln] = h[i]; else Len[LIS_L[i]]=min(Len[LIS_L[i]],h[i]); } k = Ln;//记录最长LIS Len[0]=1000000001;Ln=0; for(int i=n;i>=1;--i){ LIS_R[i] = Test_R(h[i]) + 1; if(Ln < LIS_R[i]) Len[++Ln] = h[i]; else Len[LIS_R[i]]=max(Len[LIS_R[i]],h[i]); } } //树状数组 int BIT_L[maxn<<1],BIT_R[maxn<<1]; void Add_L(int x,int v){ while(x <= Rn){ BIT_L[x]=max(BIT_L[x],v); x+=x&-x; } } int Query_L(int x){ int ANS = 0; while(x > 0){ ANS = max(ANS,BIT_L[x]); x-=x&-x; } return ANS; } void Add_R(int x,int v){ while(x > 0){ BIT_R[x]=max(BIT_R[x],v); x-=x&-x; } } int Query_R(int x){ int ANS = 0; while(x <= Rn){ ANS = max(ANS,BIT_R[x]); x+=x&-x; } return ANS; } int Count[maxn];//每种位置的数的数量 int pos[maxn];//第i个数在LIS中的位置,0表示不在LIS中 int L[maxn],R[maxn];//记录左右的答案 int main(void) { while(~scanf("%d%d",&n,&m)){ for(int i=1;i<=n;++i) scanf("%d",&h[i]),Rank[i]=h[i],List[i].clear(); //读取询问 for(int i=1;i<=m;++i){ scanf("%d%d",&a[i],&b[i]); List[a[i]].push_back(Node(i,b[i])); Rank[n+i]=b[i]; } //离散化 Rank[n+m+1]=0;Rank[n+m+2]=1000000001; SetRank(n+m+2); //建立LIS数组 LIS(); //计算Count和pos memset(Count,0,sizeof(Count)); for(int i=1;i<=n;++i){ int L = LIS_L[i],R = LIS_R[i]; if(L+R==k+1){ ++Count[pos[i]=L]; } else pos[i]=0; } //开始计算 memset(BIT_L,0,sizeof(BIT_L)); for(int i=1;i<=n;++i){ for(list<Node>::iterator it=List[i].begin();it!=List[i].end();++it){ L[it->id]=Query_L(GetRank(it->v)-1); } Add_L(GetRank(h[i]),LIS_L[i]); } memset(BIT_R,0,sizeof(BIT_R)); for(int i=n;i>=1;--i){ for(list<Node>::iterator it=List[i].begin();it!=List[i].end();++it){ R[it->id]=Query_R(GetRank(it->v)+1); } Add_R(GetRank(h[i]),LIS_R[i]); } for(int i=1;i<=m;++i){ int ANS1 = 1 + L[i]+R[i]; int ANS2 = Count[pos[a[i]]]!=1?k:k-1; printf("%d\n",max(ANS1,ANS2)); } } return 0; }
相关文章推荐
- 书评:《算法之美( Algorithms to Live By )》
- 动易2006序列号破解算法公布
- C#数据结构之顺序表(SeqList)实例详解
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- Lua教程(七):数据结构详解
- 解析从源码分析常见的基于Array的数据结构动态扩容机制的详解
- 超大数据量存储常用数据库分表分库算法总结
- C#数据结构与算法揭秘二
- C#冒泡法排序算法实例分析
- C#数据结构之队列(Quene)实例详解
- C#数据结构揭秘一
- C#数据结构之单链表(LinkList)实例详解
- 算法练习之从String.indexOf的模拟实现开始
- C#算法之关于大牛生小牛的问题
- C#实现的算24点游戏算法实例分析
- c语言实现的带通配符匹配算法
- 数据结构之Treap详解
- 浅析STL中的常用算法
- 算法之排列算法与组合算法详解