数据结构小总结(成都磨子桥技工学校数据结构前12题)
[pixiv] https://www.pixiv.net/member_illust.php?mode=medium&illust_id=34352147
暑假的作業,頹頹的我總算是寫完了
線段樹
線段樹是一個高級玩意,不僅可以求區間和,區間最大等等的簡單問題,靈活運用還有好多變種。自從學了主席樹,知道了null自環這種東西后,用在線段樹上也是得心應手
c3
給一個長為N的數列,有M次操作,每次操作是以下兩種之一:
(1)修改數列中的一個數
(2)求數列中某連續一段的和
赤裸裸的線段樹
c4
給一個長為N的數列,有M次操作,每次操作時以下三種之一:
(1)修改數列中的一個數
(2)求數列中某連續一段所有數的兩兩乘積的和 mod 1000000007
(3)求數列中某連續一段所有相鄰兩數乘積的和 mod 1000000007
數據劇毒無比,有負數,取模就出問題了。對于區間維護答案,主要就是如何合并區間。操作3好合并,只要記錄每個區間的頭、尾的數,把左右兒子區間的和加起來,再加上中間兩個數的乘積。關鍵是操作2,要是沒有見識過這個腦筋急轉彎,我可能一輩子都不會:
給出N個數, 每次可以合并兩個數, 合并的代價是兩個數的乘積, 合并得到的數是兩個數的和。
問最后把所有數合并成一個數的最小代價。 求這個最小代價對10^9+7取模的結果。
N <= 5000000。
題解是:
顯然無論怎么合并答案都是一樣的, 任意兩個數的乘積恰好會對答案貢獻一次。
直接搞就好了
于是這道題的操作2就迎刃而解了
由于這道題坑很多,我就不放我wa掉的代碼了
大神的AC代碼
c5
給一個長為N的數列,有M次操作,每次操作是以下兩種之一:
(1)將某連續一段同時改成一個數
(2)求數列中某連續一段的和
也是很基本的線段樹
c6
給一個長為N的數列,有M次操作,每次操作是以下兩種之一:
(1)修改數列中的一個數
(2)求數列中有多少個數比它前面的數都大
其實是裸的樓房重建,線段樹代碼見這里
然后聽說這道題用分塊也能過,為了練習練習,這次就用分塊了
c7
給一個長為N的數列,有M次操作,每次操作是以下兩種之一:
(1)修改數列中的一個數
(2)求數列中某個值出現了多少次
在做c8之前是沒有想到用值域線段樹的,以為會爆空間(int級別的),就偷了個懶,用map
如何處理值域線段樹的空間問題,見c8
c8
給一個長為N的數列,有M次操作,每次操作是以下三種之一:
(1)插入一個數,若已存在則忽略
(2)刪除一個數,若不存在則忽略
(3)求數列中任意兩數之差絕對值的最小值
先不考慮空間問題,我一來就想到值域線段樹。計算存在的數與最近的前后兩數的差值的最小值。每個區間儲存存在的最小數和最大數。合并時,左區間的ans,右區間的ans,左區間最大數和右區間的最小數的差,取min。
那么怎么處理空間大小問題呢?數據是2^31,但N,M的范圍是10^5,也就是說最少都有2^31-10^5的數根本和此題無關,是浪費空間。其維護的值都是一樣的,那么為什么不指向同一個空間呢?于是就把主席樹里的null搬過來,不存在的值就由null代替
然后我猜數據里有負數,結果在劃分區間是沒注意向上還是向下取整的問題,結果T掉了,全部都加上正無窮就好了。之后又沒開longlong,int加爆了。。。orz
c10
給一個長為N的數列,有M次操作,每次操作是以下兩種之一:
(1)修改數列中的一個數
(2)求數列中第K小的值
第k大問題,想來都是線段樹。由于每次要修改數據,離散化不太可能,那么就用上文提到的null來節省空間(哎呀我真是太機智了,自己yy出來的誒,也算是進步吧)
#include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std;const ll N=100000+5; const ll oo=2147483646;struct Node {Node *ls,*rs;ll cnt; }*root,*null,pool[N*80],*tail=pool; ll n,m,a[N];Node *newnode(){Node *rt=++tail;rt->ls=rt->rs=null;rt->cnt=0;return rt; } void modify(Node *&nd,ll le,ll ri,ll pos,ll val){if(nd==null) nd=newnode();if(le==ri){nd->cnt+=val;return ;}ll mid=(le+ri)>>1;if(pos<=mid) modify(nd->ls,le,mid,pos,val);else modify(nd->rs,mid+1,ri,pos,val);nd->cnt=nd->ls->cnt+nd->rs->cnt; } ll query(Node *nd,ll le,ll ri,ll pos){if(le==ri)return le;ll mid=(le+ri)>>1;if(pos<=nd->ls->cnt) return query(nd->ls,le,mid,pos);else return query(nd->rs,mid+1,ri,pos-nd->ls->cnt); } int main(){null=++tail;null->ls=null->rs=null;null->cnt=0;root=null;scanf("%lld%lld",&n,&m);for(ll i=1;i<=n;i++){scanf("%lld",&a[i]);modify(root,0,oo+oo,a[i]+oo,1);}while(m--){char opt[2];scanf("%s",opt);if(opt[0]=='Q'){ll x;scanf("%lld",&x);printf("%lld\n",query(root,0,oo+oo,x)-oo);}else {ll x,y;scanf("%lld%lld",&x,&y);modify(root,0,oo+oo,a[x]+oo,-1);modify(root,0,oo+oo,y+oo,1);a[x]=y;}}return 0; }總結:
線段樹可膩害可膩害了,其實很多的區間問題都可以解決,唯一的關鍵點就是如何快速的區間合并。俗話說“以不變應萬變”,只要解決區間合并的方法就可以了。
主席樹
c2
給一個空數列,有M次操作,每次操作是以下三種之一:
(1)在數列后加一個數
(2)求數列中某位置的值
(3)撤銷掉最后進行的若干次操作(1和3)
感覺像是數組,又感覺像是主席樹。但是數組又不易用指針寫,于是一氣之下殺雞用牛刀,用線段樹來解決數列問題。。。1A
#include<cstdio> #include<cstring> #include<algorithm> using namespace std;const int N=100000+5;struct Node{Node *ls,*rs;int val; }*root[N],*null,pool[N*50],*tail=pool; int len[N],m;Node *newnode(){Node *rt=++tail;rt->ls=rt->rs=null;rt->val=0;return rt; } void insert(Node *np,Node *&nd,int le,int ri,int pos,int val){nd=newnode();nd->ls=np->ls,nd->rs=np->rs;nd->val=np->val;if(le==ri){nd->val=val;return ;}int mid=(le+ri)>>1;if(pos<=mid) insert(np->ls,nd->ls,le,mid,pos,val);else insert(nd->rs,nd->rs,mid+1,ri,pos,val); } int query(Node *nd,int le,int ri,int pos){if(le==ri) return nd->val;int mid=(le+ri)>>1;if(pos<=mid) return query(nd->ls,le,mid,pos);else return query(nd->rs,mid+1,ri,pos); } int main(){null=++tail;null->ls=null->rs=null;null->val=0;root[0]=null;scanf("%d",&m);int cnt=0;while(m--){char opt[2];int x;scanf("%s%d",opt,&x);if(opt[0]=='A'){cnt++;len[cnt]=len[cnt-1]+1;insert(root[cnt-1],root[cnt],1,N,len[cnt],x);}else if(opt[0]=='Q'){printf("%d\n",query(root[cnt],1,N,x));}else{cnt++;root[cnt]=root[cnt-x-1];len[cnt]=len[cnt-x-1]; }}return 0; }c11
給一個長為N的數列,有M次操作,每次操作是以下兩種之一:
(1)修改數列中的一個數
(2)求某次操作后連續一段的和
裸主席樹,就當練手速吧
#include<cstdio> #include<cstring> #include<algorithm> using namespace std;const int N=100000+5;struct Node{Node *ls,*rs;int sum; }*root[N],*null,pool[N*80],*tail=pool; int n,m,a[N];Node *newnode(){Node *rt=++tail;rt->ls=rt->rs=null;rt->sum=0;return rt; } void build(Node *&nd,int le,int ri){nd=newnode();if(le==ri){nd->sum=a[le];return ;}int mid=(le+ri)>>1;build(nd->ls,le,mid);build(nd->rs,mid+1,ri);nd->sum=nd->ls->sum+nd->rs->sum; } void insert(Node *ne,Node *&nd,int le,int ri,int pos,int val){nd=newnode();nd->ls=ne->ls,nd->rs=ne->rs;if(le==ri){nd->sum=val;return ;}int mid=(le+ri)>>1;if(pos<=mid) insert(ne->ls,nd->ls,le,mid,pos,val);else insert(ne->rs,nd->rs,mid+1,ri,pos,val);nd->sum=nd->ls->sum+nd->rs->sum; } int query(Node *nd,int le,int ri,int L,int R){if(L<=le&&ri<=R) return nd->sum;int mid=(le+ri)>>1,rt=0;if(L<=mid) rt+=query(nd->ls,le,mid,L,R);if(mid<R) rt+=query(nd->rs,mid+1,ri,L,R);return rt; } int main(){null=++tail;null->ls=null->rs=null;null->sum=0;scanf("%d%d",&n,&m);for(int i=1;i<=n;i++) scanf("%d",&a[i]);build(root[0],1,n);for(int i=1;i<=m;i++){char opt[2];scanf("%s",opt);if(opt[0]=='M'){int x,y;scanf("%d%d",&x,&y);insert(root[i-1],root[i],1,n,x,y);}else{int x,y,z;scanf("%d%d%d",&x,&y,&z);printf("%d\n",query(root[z],1,n,x,y));root[i]=root[i-1];}}return 0; }c12
給一個長為N的數列,有M次操作,操作僅有一種:
求數列中某連續一段中第K小的值
還是裸的主席樹,沒錯我是來掛代碼的
#include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std;const ll N=100000+5; const ll oo=2147483646;struct Node {Node *ls,*rs;ll cnt; }*root[N],*null,pool[N*100],*tail=pool; ll n,m,a[N];Node *newnode(){Node *rt=++tail;rt->ls=rt->rs=null;rt->cnt=0;return rt; } void insert(Node *ne,Node *&nd,ll le,ll ri,ll pos){nd=newnode();nd->ls=ne->ls,nd->rs=ne->rs;nd->cnt=ne->cnt+1;if(le==ri) return;ll mid=(le+ri)>>1;if(pos<=mid) insert(ne->ls,nd->ls,le,mid,pos);else insert(ne->rs,nd->rs,mid+1,ri,pos); } ll query(Node *ne,Node *nd,ll le,ll ri,ll k){if(le==ri) return le;ll mid=(le+ri)>>1;ll lsc=nd->ls->cnt - ne->ls->cnt;if(k<=lsc) return query(ne->ls,nd->ls,le,mid,k);else return query(ne->rs,nd->rs,mid+1,ri,k-lsc); } int main(){null=++tail;null->ls=null->rs=null;null->cnt=0;root[0]=null;scanf("%lld%lld",&n,&m);for(ll i=1;i<=n;i++){scanf("%lld",&a[i]);insert(root[i-1],root[i],0,oo+oo,a[i]+oo);}while(m--){ll x,y,z;scanf("%lld%lld%lld",&x,&y,&z);printf("%lld\n",query(root[x-1],root[y],0,oo+oo,z)-oo);}return 0; }數組
c0,c1
太簡單啦,寒假做的
平衡樹
c9
給一個長為N的數列,有M次操作,每次操作是以下兩種之一:
(1)刪除某個位置的數
(2)求數列某位置的值
一來就想到treap,但是感覺大材小用了,就偷了個懶
#include<cstdio> #include<cstring> #include<algorithm> #include<vector> using namespace std;vector<int> vec; int n,m;int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){int a;scanf("%d",&a);vec.push_back(a);}while(m--){char opt[2];int x;scanf("%s%d",opt,&x);x--;if(opt[0]=='D')vec.erase(vec.begin()+x);else printf("%d\n",vec[x]);}return 0; }好啦,就是這樣
轉載于:https://www.cnblogs.com/LinnBlanc/p/7763147.html
總結
以上是生活随笔為你收集整理的数据结构小总结(成都磨子桥技工学校数据结构前12题)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Docfetcher – 本地硬盘上的
- 下一篇: MacBook安装telnet工具和使用