模拟赛 yjqb
?
對于這種“不能交叉”的條件,不是很好處理。那么就考慮一下dp
dp[i][j]表示,考慮A中用前i個,考慮連接B中用前j個,最大匹配。(類似LCS的DP)
轉移:dp[i][j]=max(dp[i][j-1],dp[i-1][j])當li<=j<=ri時,dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1)
這樣可以保證一定不會交叉,而且不會共用匹配點
O(N^2)
?
看起來非常無法優化。而且我們還沒有好好利用區間連邊的特點。
觀察轉移的特點,
一個發現是,這是一個前綴取max,并且某些dp數值的位置+1
由于要和前面的i取max,
那么,把函數鍵值化,
對于考慮到第i個點,
圖像是:
一個分段函數!
考慮如果新加入一個點i的話,在此基礎上造成什么影響。
為了方便理解,考慮用滾動數組
?
dp[j]=max(dp[j],dp[j-1]+1)
這個dp[j]可以直接理解為上一次留下的dp值。(當然轉移是倒序循環)
dp[j]=max(dp[j-1],dp[j])這個要正序循環
發現,對于分段函數造成的影響,是一段區間!
由于dp[j]=max(dp[j],dp[j-1]+1),注意每個分段函數的左端點是不會變化的。
并且,+1的轉移都是dp[j]=max(dp[j],dp[j-1]+1)
所以,分段函數的落差都恰好是1!!!
?
如果可以維護好分段函數,那么最后最高的函數就是ans
怎么維護?
發現這個函數的變化,本質上是把更新區間中涉及到的分段函數向右移動一步,再向上移動一步得到的新的圖像!
于是可以打標記了!
這個函數的區間提取,如果用線段樹做的話,提取會非常麻煩。而且左移上移怎么處理?!?!
這么靈活的移動,只能交給平衡樹了!
ywy_c_asm的大力討論法:
用三元組[l,r,val]表示每個分段函數的左右端點和高度(函數值)
很多麻煩的事情:
1.邊界涉及到函數分離,函數合并。
2.邊界可能是某些函數的左端點,
3.和后面的合并?沒有后繼怎么辦?
4.[L,R]只有一個分段函數?要特判
5.[L,R]有兩個分段函數?由于不能直接把后面的函數合并到前驅再--r那么簡單(其實好像可以?)反正特判比較保險
6.[L,R]有多個分段函數?這時候就要區間打標記了。
7.merge函數那個并入哪一個?
8.split函數,從哪里斷開?剩下的l,r是什么?
還有一些splay的基本操作(我寫的splay)
1.pre,bac前驅后繼,記得pushdown
2.kth,記得pushdown
3.tag的標記打好。
4.左右位置放上空節點方便提取區間。
。。。。。。。。。
還有一堆細節
。。。。。。。。。
?
放上代碼:
大概4+4+4=12種討論?
刪掉注釋250行左右
#include<bits/stdc++.h> #define reg register int #define il inline #define ls t[x].ch[0] #define rs t[x].ch[1] #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){char ch;x=0;bool fl=false;while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);for(x=numb;isdigit(ch=getchar());x=x*10+numb);(fl==true)&&(x=-x); } namespace Miracle{ const int N=100000+5; const int nd=3; const int st=1; int n; struct node{int l,r,v;int fa,ch[2];int tag;node(){}node(int ll,int rr,int vv){l=ll,r=rr,v=vv;ch[0]=ch[1]=0;fa=0;tag=0;}void op(){cout<<" left "<<l<<" right "<<r<<" val "<<v<<endl;cout<<" father "<<fa<<" son1 "<<ch[0]<<" son2 "<<ch[1]<<" tag "<<tag<<endl;} }t[N]; int cnt; int rt; void tag(int x,int c){t[x].tag+=c;t[x].v+=c;t[x].l+=c;t[x].r+=c; } void pushdown(int x){if(!t[x].tag) return; // cout<<" pushdown "<<x<<" "<<t[x].tag<<endl; tag(ls,t[x].tag);tag(rs,t[x].tag);t[x].tag=0; } void rotate(int x){int y=t[x].fa,d=t[y].ch[1]==x;t[t[y].ch[d]=t[x].ch[!d]].fa=y;t[t[x].fa=t[y].fa].ch[t[t[y].fa].ch[1]==y]=x;t[t[x].ch[!d]=y].fa=x; } void splay(int x,int f){while(t[x].fa!=f){int y=t[x].fa,z=t[y].fa;if(z!=f){rotate((t[z].ch[0]==y)&&(t[y].ch[0]==x)?y:x);}rotate(x);}if(f==0) rt=x; } int pre(int x){splay(x,0);x=t[x].ch[0];if(!x) return -1;pushdown(x);while(t[x].ch[1]) {x=t[x].ch[1];pushdown(x);}return x; } int bac(int x){ // cout<<" fin bac "<<x<<" "<<t[x].ch[0]<<endl;splay(x,0);x=t[x].ch[1]; // cout<<" ch[1] "<<x<<" "<<t[x].ch[0]<<endl;if(!x) return -1;pushdown(x);while(t[x].ch[0]) {x=t[x].ch[0];pushdown(x);}return x; } int kth(int k){ // cout<<" find kth "<<k<<endl;int x=rt;while(x){pushdown(x);// cout<<" xx "<<x<<endl;t[x].op();if(t[x].l<=k&&k<=t[x].r) return x;else if(t[x].l>k) x=t[x].ch[0];else if(t[x].r<k) x=t[x].ch[1];}return -1;//warning !!! } void merge(int x,int y){//cout<<" merge "<<x<<" "<<y<<endl;splay(x,0);splay(y,x);t[t[x].ch[1]=t[y].ch[1]].fa=x;t[x].r=t[y].r; } void split(int x,int l,int r){++cnt;t[cnt]=node(l,r,t[x].v);t[t[cnt].ch[1]=t[x].ch[1]].fa=cnt;t[cnt].fa=x;t[x].ch[1]=cnt;t[x].r=l-1;//warning!! } void wrk(int L,int R){ // cout<<" wrking ---------------------"<<L<<" "<<R<<endl;int lc=kth(L),rc=kth(R);//cout<<" lc "<<lc<<endl;t[lc].op();//cout<<" rc "<<rc<<endl;t[rc].op();if(lc==rc){// cout<<" Sol 1*****"<<endl;if(L==R){// cout<<" 1.1%%"<<endl;if(t[lc].l==L){ // int pr=pre(lc); // if(pr==st){ // int bc=bac(lc); // if(bc==nd){ // ++t[lc].v; // }else{ // merge(lc,bc); // // } // }return;}else if(t[lc].r==L){int bc=bac(lc);if(bc==nd){//las cur split(lc,L,R);++t[cnt].v;}else{t[bc].l=L;t[lc].r=L-1;}return;}else{//cout<<" 1.1.3$$$ "<<endl;int bc=bac(lc);// cout<<" bc "<<bc<<endl;if(bc==nd){split(lc,L,t[lc].r);++t[cnt].v;}else{t[bc].l=L;t[lc].r=L-1;}return;}}else{if(t[lc].l==L){int bc=bac(lc);if(bc==nd){//las cursplit(lc,t[lc].l+1,t[lc].r);++t[cnt].v;}else{t[bc].l=t[lc].l+1;t[lc].r=t[lc].l;}return;}else{int bc=bac(lc);if(bc==nd){//las cur split(lc,L,t[lc].r);++t[cnt].v;}else{t[bc].l=L;t[lc].r=L-1;}return;}}return;}int pr=pre(rc);if(pr==lc){if(t[lc].l==L&&t[rc].l==R){t[lc].r=L;t[rc].l=L+1;}else if(t[lc].l==L){int bc=bac(rc);if(bc==nd){split(rc,t[rc].l+1,t[rc].r);++t[cnt].v;}else{t[bc].l=t[rc].l+1;t[rc].r=t[rc].l;}t[rc].l=L+1;t[lc].r=L;}else if(t[rc].l==R){t[rc].l=L;t[lc].r=L-1;}else{int bc=bac(rc);if(bc==nd){split(rc,t[rc].l+1,t[rc].r);++t[cnt].v;}else{t[bc].l=t[rc].l+1;t[rc].r=t[rc].l;}t[rc].l=L;t[lc].r=L-1;}}else{//cout<<" Sol 3*******"<<endl;if(t[rc].l==R){merge(pr,rc);rc=pr;t[rc].r--;}else{// cout<<" 3.1.2$$$"<<endl;int bc=bac(rc);// cout<<" bac "<<bc<<endl;if(bc==nd){t[rc].r--;}else{merge(rc,bc);t[rc].r--;}}if(t[lc].l==L){split(lc,L,t[lc].r);t[lc].r=L;lc=cnt;}else{split(lc,L-1,t[lc].r);t[lc].r=L-1;lc=cnt;}int LL=pre(lc),RR=bac(rc);splay(LL,0);splay(RR,LL);//cout<<" LL "<<LL<<endl;t[LL].op();// cout<<" RR "<<RR<<endl;t[RR].op();tag(t[RR].ch[0],1);} } int calc(){splay(nd,0);int cur=pre(nd);return t[cur].v; } int main(){rd(n);rt=1;t[++cnt]=node(-1,-1,0);t[cnt].fa=0;t[cnt].ch[1]=cnt+1;++cnt;t[cnt]=node(0,n,0);t[cnt].fa=1;t[cnt].ch[1]=cnt+1;++cnt;t[cnt]=node(n+1,n+1,0);t[cnt].fa=2;// cout<<t[1].fa<<" "<<t[1].ch[0]<<" "<<t[1].ch[1]<<endl; // cout<<t[2].fa<<" "<<t[2].ch[0]<<" "<<t[2].ch[1]<<endl; // cout<<t[3].fa<<" "<<t[3].ch[0]<<" "<<t[3].ch[1]<<endl;int L,R;for(reg i=1;i<=n;++i){rd(L);rd(R);wrk(L,R);// cout<<" after wrk "<<cnt<<endl; }printf("%d\n",calc());return 0; }} signed main(){Miracle::main();return 0; }/*Author: *Miracle*Date: 2018/12/24 13:54:20 */ View Code?
太TMD麻煩了。。。。。(要不是為了鍛煉碼力才不寫)
發現之前的方法麻煩的地方在于,維護[l,r]這個區間要來回討論。函數值+1要討論。左端點要討論。split,merge要討論。。。
落差都是1,怎么利用呢?
用一些點來維護分段函數!
每個點表示這個分段函數的左端點。維護點的橫坐標
修改的時候,
[l,r)的左端點集體右移,加入一個l點,把>=r最小的點刪除。沒了。
原因是,我們不記錄高度,一個函數值左邊的點的個數,就是函數值的大小!
所以,插入一個點相當于對后面的所有點高度+1,只需要將點向右平移。
同時,代替合并函數值的是,把>=r的點刪除掉。
一個關鍵點在r的位置,并不能+1,所以是開區間。
最后點的個數就是函數值!
?
?
總結:
1.DP首先要想到。觀察轉移,得到分段函數的性質
2.分段函數的移動,可以模式化,所以可以打標記。
3.平衡樹維護分段函數,以及一些小技巧。
?
轉載于:https://www.cnblogs.com/Miracevin/p/10171156.html
總結
- 上一篇: Nginx安装echo模块
- 下一篇: react native 网络请求 ax