zoukankan      html  css  js  c++  java
  • 数据结构|序列问题与树上问题小结

    数据结构小结

    好累啊这几天沉迷数据结构
    高数被我鸽几天了,单词又背了遍abandon...
    总结一下这几天沉迷的成果,这些东西虽然好玩,但是留给我的时间不多了,2020都过了好多天了
    感觉要是不看爱情公寓,可以再多刷个两道题。。

    珂朵莉树

    之前写过了,https://www.cnblogs.com/fisherss/p/12182869.html

    线段树常用模板

    单点更新,区间更新,维护最值

    https://www.cnblogs.com/fisherss/p/10920642.html

    P3373 线段树维护区间乘、区间加

    题解:https://www.luogu.com.cn/problemnew/solution/P3373
    多个标记分清楚,标记的优先级顺序

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn = 1e5+10;
    ll mod;
    ll a[maxn];
    
    struct node{
    	ll v,mul,add;
    }tree[maxn*4+10];
    
    void pushup(int o){
    	tree[o].v = (tree[o<<1].v + tree[o<<1|1].v)%mod;
    }
    
    void pushdown(int o,int l,int r){
    	//这里要干什么
    	int mid = (l+r)>>1;
    	//更新子节点的sum: 子节点sum = v*父节点mul + 父节点标记的add值 * 子节点区间长度 
    	tree[o<<1].v = (tree[o<<1].v * tree[o].mul + tree[o].add * (mid-l+1))%mod;
    	tree[o<<1|1].v = (tree[o<<1|1].v * tree[o].mul + tree[o].add * (r-(mid+1)+1))%mod;
    	//更新子节点的mul
    	tree[o<<1].mul = (tree[o<<1].mul * tree[o].mul)%mod;
    	tree[o<<1|1].mul = (tree[o<<1|1].mul * tree[o].mul)%mod;
    	//更新子节点的add
    	tree[o<<1].add = (tree[o<<1].add * tree[o].mul + tree[o].add)%mod; //先成后加
    	tree[o<<1|1].add = (tree[o<<1|1].add * tree[o].mul + tree[o].add)%mod;
    	//清除父节点的标记 
    	tree[o].mul = 1;
    	tree[o].add = 0; 
    }
    
    void build(int o,int l,int r){
    	//初始化所有结点的lazy标记 
    	tree[o].mul = 1;
    	tree[o].add = 0;
    	if(l == r){ //叶节点 
    		tree[o].v = a[l]%mod;
    		return;
    	}
    	int mid = (l+r)>>1;
    	build(o<<1,l,mid);
    	build(o<<1|1,mid+1,r);
    	pushup(o);
    }
    
    //区间乘 
    void update1(int o,int l,int r,int ql,int qr,ll k){
    	if(ql<=l && r<=qr){
    		tree[o].v = (tree[o].v * k)%mod;
    		tree[o].mul = (tree[o].mul * k)%mod;
    		tree[o].add = (tree[o].add * k)%mod;
    		return; 
    	}
    	pushdown(o,l,r);
    	int mid = (l+r)>>1;
    	if(ql<=mid) update1(o<<1,l,mid,ql,qr,k);
    	if(qr>mid) update1(o<<1|1,mid+1,r,ql,qr,k);
    	pushup(o);
    }
    
    //区间加 
    void update2(int o,int l,int r,int ql,int qr,ll k){
    	if(ql<=l && r<=qr){
    		tree[o].v = (tree[o].v + (r-l+1)*k)%mod;
    		tree[o].add = (tree[o].add + k)%mod;
    		return;
    	}
    	pushdown(o,l,r);
    	int mid = (l+r)>>1;
    	if(ql<=mid) update2(o<<1,l,mid,ql,qr,k);
    	if(qr>mid) update2(o<<1|1,mid+1,r,ql,qr,k);
    	pushup(o);
    }
    
    //区间查询 
    ll querysum(int o,int l,int r,int ql,int qr){
    	if(ql<=l && r<=qr){
    		return tree[o].v%mod; 
    	}
    	pushdown(o,l,r);
    	int mid = (l+r)>>1;
    	ll ans = 0;
    	if(ql <= mid) ans = (ans + querysum(o<<1,l,mid,ql,qr))%mod;
    	if(qr >= mid+1) ans = (ans + querysum(o<<1|1,mid+1,r,ql,qr))%mod;
    	return ans%mod;
    }
    
    int main(){
        int n, m;
        scanf("%d%d%d", &n, &m, &mod);
        for(int i=1; i<=n; i++){
            scanf("%lld", &a[i]);
        }
        build(1, 1, n);
        while(m--){
            int opt;
            scanf("%d", &opt);
            int x, y;
            long long k;
            if(opt==1){ //区间乘 
                scanf("%d%d%lld", &x, &y, &k);
                update1(1, 1, n, x, y, k);
            }else if(opt==2){ //区间加 
                scanf("%d%d%lld", &x, &y, &k);
                update2(1, 1, n, x, y, k);
            }else{ //查询区间和 
                scanf("%d%d", &x, &y);
                printf("%lld
    ", querysum(1, 1, n, x, y));
            }
        }
        return 0;
    }
    

    P4145 线段树区间开根号

    考虑到开根号是个很快的操作,一个区间开着开着就变成了0或者1
    维护下线段树的某个区间是不是全0或者全1,是的话不管,不是的话暴力开根号即可。

    
    //区间修改 转变成了 →单点暴力开根号↓
    //每进入一个最小区间(只有一个值)开根号,更新标记,向上up更新
    void change(int o,int l,int r,int ql,int qr) {
    	if(setv[o]) return;
    	if(l==r) { //每进入一个最小区间(l==r时区间SSEd@只有一个值)
    		sum[o]=(ll)sqrt(sumv[o]);//开根号
    		if(sumv[o]==1||sumv[o]==0) setv[o]=1; //新标记
    		return;
    	}
    	int mid = (l+r)>>1;
    	if(ql<=mid) change(lson,mid,ql,qr);
    	if(qr>mid) change(rson,mid+1,r,ql,qr);
    	pushup(o); //向上up更新
    }
    

    P4513 线段树维护最大子段和

    这道题学习参考点:主要是"合并"
    对于每个区间,维护一个左边的最大前缀,右边的最大后缀,以及区间内部的总答案
    每次合并的时候,即答案选取左子区间的max,右子区间的max,或者左子区间的最大后缀,右子区间的最大前缀即可
    题解1:https://www.luogu.com.cn/blog/41302/solution-p4513
    题解2:https://www.luogu.com.cn/blog/user52559/solution-p4513
    借个图

    P2572 珂朵莉树|线段树维护复杂信息

    这道题还可以用珂朵莉树做,暴力优雅。
    线段树题解1:https://www.luogu.com.cn/blog/QVQ/solution-p2572
    线段树的合并思想类似小白逛公园,分左、右、跨越区间三种清空;
    优先级的问题类似于区间乘那题乘法优先,这里就是赋值优先
    借个图

    P1712 双指针 + 线段树(待补)

    题目:https://www.luogu.com.cn/problem/P1712
    题解:https://www.luogu.com.cn/blog/user5680/solution-p1712

    然后接触到,线段树动态开点

    思想学习:https://blog.csdn.net/lvmaooi/article/details/79729437
    理解了,动态开点就是 动态给结点分配编号,不用先build建树了
    动态开点,每个结点最多开logn个,所以空间复杂度就是O(nlogn)
    *又听说,类似主席树的写法,到时候学到再补

    P1908 逆序对,线段树+动态开点做法

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define lson l,mid,tree[root].l
    #define rson mid+1,r,tree[root].r
    #define ls tree[root].l
    #define rs tree[root].r
    const int maxn = 5e5+10;
    const int inf = 1e9+5;
    
    //为什么要动态开点:因为这道题数字个数大1e9,不动态开点就先要离散化再建线段树 
    //静态开点的左孩子是root*2,右孩子是root*2+1,现在按编号分配 
    
    struct node{
    	int l,r,sum;
    }tree[maxn*32];
    
    int cnt = 1;
    
    //合并更新父节点 
    void pushup(int root){
    	tree[root].sum=tree[tree[root].l].sum+tree[tree[root].r].sum;
    } 
    
    void update(int &root,int l,int r,int pos){
    	if(!root) root = ++cnt; //动态分配"结点编号"  
    	if(l == r){
    		tree[root].sum++;
    		return;
    	}
    	int mid = (l + r)>>1;
    	if(pos <= mid) update(tree[root].l,l,mid,pos); //要更新的pos点在前半段区间 
    	else update(tree[root].r,mid+1,r,pos); //要更新的pos点在后半段区间
    	pushup(root);
    }
    
    ll query(int root,int l,int r,int ql,int qr){
    	ll ans = 0;
    	if(ql <= l && r <= qr){ //如果完全包含(l,r)区间 
    		return tree[root].sum;
    	}
    	int mid = (l + r)>>1;
    	if(ql <= mid) ans += query(tree[root].l,l,mid,ql,qr); //要查询的区间包含了左边一侧 查左边 
    	if(qr > mid) ans += query(tree[root].r,mid+1,r,ql,qr); //要查询的区间包含了右边一侧 查右边 
    	return ans;
    } 
    
    int main(){
    	int n;
    	cin>>n;
    	ll ans = 0;
    	int root = 1;
    	for(int i=1;i<=n;i++){
    		int x;
    		cin>>x;
    		ans += query(1,1,inf,x+1,inf); //查询当前输入下已经比x大的数右多少个,即(x+1,inf)范围内已插入数的个数 
    		update(root,1,inf,x); //x位置上个数+1 
    	} 
    	cout<<ans<<endl;
    	return 0;
    }
    
    /*
    参考链接:https://blog.csdn.net/qq_43906000/article/details/102155429
    类似题目:https://blog.csdn.net/u012972031/article/details/88751811 
    */
    

    P1908 线段树 + 离散化做法

    #include<bits/stdc++.h>
    #define fi first
    #define se second
    #define INF 0x3f3f3f3f
    #define ll long long
    #define ld long double
    #define mem(ar,num) memset(ar,num,sizeof(ar))
    #define me(ar) memset(ar,0,sizeof(ar))
    #define lowbit(x) (x&(-x))
    #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    #define lcm(a,b) ((a)*(b)/(__gcd((a),(b))))
    #define maxn 500010
    #define mod 1000000007
    using namespace std;
    
    ll n, tree[maxn << 2], arr[maxn], temp[maxn], ans;
    
    void pushup(int root) {
        tree[root] = tree[root << 1] + tree[root << 1 | 1];
    }
    
    //初始化各个值都是0 
    void build(int root,int l,int r){
    	if(l == r){
    		tree[root] = 0;
    		return; 
    	}
    	int mid = (l+r)>>1;
    	build(root << 1,l,mid);
    	build(root <<1 | 1,mid+1,r);
    	pushup(root);
    }
    
    void update(int root,int l, int r,int pos) {
        if(l == r) {
            tree[root]++;
            return;
        }
        int mid = (l + r) >> 1;
        if(mid >= pos) update(root << 1, l, mid, pos);
        else update(root << 1 | 1,mid + 1, r, pos);
        pushup(root);
    }
    
    ll query( int root,int ql, int qr, int l, int r) {
        if(ql <= l && r <= qr) {
            return tree[root];
        }
        int mid = (r + l) >> 1;
        ll ans = 0;
        if(ql <= mid) ans += query(root << 1, ql, qr, l, mid);
        if(qr > mid) ans += query(root << 1 | 1,ql, qr, mid + 1, r);
        return ans;
    }
    
    int main() {
        cin >> n;
        for(int i = 1; i <= n; i++)
            cin >> arr[i], temp[i] = arr[i];
        //离散化开始
        sort(temp + 1, temp + n + 1);
        int num = unique(temp + 1, temp + n + 1) - temp - 1;
        for(int i = 1; i <= n; i++)
            arr[i] = lower_bound(temp + 1, temp + num + 1, arr[i]) - temp; //lower_bound - tempp 就是编号了 
        //离散化结束↑ 
    	build(1,1,50001); //这一步可以省略 无需先建树 
        for(int i = 1; i <= n; i++) {
            update(1, 1, n, arr[i]);
            ans += query(1, arr[i] + 1, n, 1, n);
        }
        cout << ans;
        return 0;
    }
    
    /*
    参考链接:https://blog.csdn.net/endeavor_g/article/details/88654684 
    */
    

    HPU校赛 线段树动态开点维护前缀和后缀和

    敲了一遍竟然过了,有点开心啊!
    https://www.cnblogs.com/fisherss/p/12104701.html

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn = 1e5+10;
    int n;
    ll a[maxn];
    int k[maxn];
    
    
    ll min(ll a,ll b){
    	if(a < b) return a;
    	else return b;
    }
    
    struct tree{
    	ll sum[maxn];
    	
    	void pushup(int root){
    		sum[root] = min(sum[root<<1],sum[root<<1|1]);
    	}
    	
    	void build(int root,int l,int r){
    		if(l == r){
    			sum[root] = 0;
    			return;
    		}
    		int mid = (l+r)>>1;
    		build(root<<1,l,mid);
    		build(root<<1|1,mid+1,r);
    		pushup(root);
    	}
    	
    	void update(int root,int l,int r,int pos,int v){
    		if(l == r){
    			sum[root] = v;
    			return;
    		}
    		int mid = (l + r) >> 1;
    		if(pos <= mid) update(root<<1,l,mid,pos,v);
    		else update(root<<1|1,mid+1,r,pos,v);
    		pushup(root);
    	}
    	
    	ll query(int root,int l,int r,int ql,int qr){
    		if(ql == 0 && qr == 0) return 0; //必须处理 查询长度为0时的边界 
    		if(ql <= l && r <= qr){
    			return sum[root];
    		}
    		int mid = (l + r) >> 1;
    		ll ans = 0x3f3f3f3f;
    		if(ql <= mid) ans = min(ans,query(root<<1,l,mid,ql,qr));
    		if(qr > mid) ans = min(ans,query(root<<1|1,mid+1,r,ql,qr));
    		return ans; 
    	}
    	
    }tp,tn;
    
    int main(){
    	cin>>n;
    	for(int i=1;i<=n;i++) cin>>k[i];
    	for(int i=1;i<=n;i++) cin>>a[i];
    	ll sum = 0;
    	tp.build(1,1,n);
    	for(int i=1;i<=n;i++){
    		sum += a[i];//前缀
    		tp.update(1,1,n,i,sum); 
    	}
    	sum = 0;
    	tn.build(1,1,n);
    	for(int i=n;i>=1;i--){
    		sum += a[i];//后缀 
    		tn.update(1,1,n,i,sum);
    	}
    	ll ans = 0;
    	//求s1 + ... + sn 
    	for(int i=1;i<=n;i++){
    		ans += sum;
    		ans -= tp.query(1,1,n,max(i-k[i]-1,0),max(i-1,0)); //除了i以外的前缀 
    		ans -= tn.query(1,1,n,min(i+1,n+1),min(i+k[i]+1,n+1)); //除了i以外的后缀
    	} 
    	cout<<ans;
    	return 0; 
    } 
    
    /*
    5
    1 2 3 4 4
    -5 1 2 3 -4
    
    
    16
    */
    

    扫描线 与 线段树问题

    讲解最好的博客1:https://blog.csdn.net/xianpingping/article/details/83032798
    HDU1542参考题解2:https://www.cnblogs.com/liwenchi/p/7259171.html

    借用一下博客1的代码,加了部分注释

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<string>
    #include<queue>
    #include<algorithm>
    #include<map>
    #include<iomanip>
    #define INF 99999999
    using namespace std;
    
    /*
    个人感觉:
    	相当于线段树在线开点 插入新点 维护sum边长 
    */ 
     
    const int MAX=200+10;
    int mark[MAX<<2];//记录某个区间的下底边个数
    double sum[MAX<<2];//记录某个区间的下底边总长度
    double value[MAX];//对x进行离散化,否则x为浮点数且很大无法进行线段树 
     
    //以横坐标作为线段(区间),对横坐标线段进行扫描
    //扫描的作用是每次更新下底边总长度和下底边个数,增加新面积 
    struct seg{//线段
    	double l,r,h;
    	int d;
    	seg(){}
    	seg(double x1,double x2,double H,int c):l(x1),r(x2),h(H),d(c){}
    	bool operator<(const seg &a)const{
    		return h<a.h;
    	}
    }s[MAX];
    
    void pushup(int o,int left,int right){ 
    	if(mark[o]) sum[o]=value[right+1]-value[left];//mark[o]!=0表示包含了整个子区间,该子区间整个线段长度可以作为底边 
    	else if(left == right)sum[o]=0;//叶子结点则底边长度为0(区间内线段长度为0) 
    	else sum[o]=sum[o<<1]+sum[o<<1|1];
    }
     
    void update(int L,int R,int d,int o,int left,int right){
    	if(L<=left && right<=R){//该区间是当前扫描线段的一部分,则该区间下底边总长以及上下底边个数差更新 
    		mark[o]+=d;//更新底边相差差个数 
    		pushup(o,left,right);//更新底边长 
    		return;
    	}
    	int mid=left+right>>1;
    	if(L<=mid)update(L,R,d,o<<1,left,mid);
    	if(R>mid)update(L,R,d,o<<1|1,mid+1,right);
    	pushup(o,left,right);
    }
    
    //二分查找 
    int search(double key,double* x,int n){
    	int left=0,right=n-1;
    	while(left<=right){
    		int mid=left+right>>1;
    		if(x[mid] == key)return mid;
    		if(x[mid]>key)right=mid-1;
    		else left=mid+1;
    	}
    	return -1;
    }
     
    int main(){
    	int n,num=0;
    	double x1,x2,y1,y2;
    	while(cin>>n,n){
    		int k=0;
    		for(int i=0;i<n;++i){
    			cin>>x1>>y1>>x2>>y2;
    			value[k]=x1; //记录树结点->但是记录的离散化前的 
    			s[k++]=seg(x1,x2,y1,1); //记录扫描线 下边位 
    			value[k]=x2;
    			s[k++]=seg(x1,x2,y2,-1); //记录扫描线 上边位 
    		}
    		sort(value,value+k);
    		sort(s,s+k);
    		int m=1;
    		for(int i=1;i<k;++i)//去重复端点 
    		    if(value[i] != value[i-1])value[m++]=value[i];
            double ans=0;
            //memset(mark,0,sizeof mark);
            //memset(sum,0,sizeof sum);如果下面是i<k-1则要初始化,因为如果对第k-1条线段扫描时会使得mark,sum为0才不用初始化的 
    		for(int i=0;i<k;++i){//扫描线段
    			int L=search(s[i].l,value,m);
    			int R=search(s[i].r,value,m)-1;
    			update(L,R,s[i].d,1,0,m-1);//扫描线段时更新底边长度和底边相差个数
    //			cout<<ans<<" "<<sum[1]*(s[i+1].h-s[i].h)<<endl;
    			ans+=sum[1]*(s[i+1].h-s[i].h);//新增加面积
    		}
    		printf("Test case #%d
    Total explored area: %.2lf
    
    ",++num,ans);
    	}
    	return 0;
    }
    /*
    这里注意下
    扫描线段时r-1:int R=search(s[i].l,value,m)-1;
    计算底边长时r+1:if(mark[n])sum[n]=value[right+1]-value[left];
    解释:假设现在有一个线段左端点是l=0,右端点是r=m-1
    则我们去更新的时候,会算到sum[1]=value[mid]-value[left]+value[right]-value[mid+1]
    这样的到的底边长sum是错误的,why?因为少算了mid~mid+1的距离,由于我们这利用了
    离散化且区间表示线段,所以mid~mid+1之间是有长度的,比如value[3]=1.2,value[4]=5.6,mid=3
    所以这里用r-1,r+1就很好理解了 
    */ 
    

    树链剖分

    学习地址1:https://www.bilibili.com/video/av4482146
    学习地址2:https://www.bilibili.com/video/av24798851
    详细博客1:https://www.cnblogs.com/chinhhh/p/7965433.html
    用法总结1:https://blog.csdn.net/qq_41730604/article/details/101453877
    树剖入门到入土的题目总结:https://www.cnblogs.com/Isaunoya/p/11619823.html

    树链剖分求LCA(模板P3379)

    我我我终于写出了自己的LCA!
    还是喜欢vector啊

    #include<bits/stdc++.h>
    using namespace std;
    
    const int maxn = 5e5+100;
    vector<int> g[maxn];
    /*  父亲,    深度,       子节点数, 重儿子,   dfs序,   dfs映射,链头,    链尾    */
    int fa[maxn],depth[maxn],sz[maxn],son[maxn],id[maxn],rk[maxn],top[maxn],bot[maxn];
    int cnt = 0;
    
    void dfs(int x,int deep){
    	depth[x] = deep;
    	sz[x] = 1;
    	for(int li = 0;li<g[x].size();li++){
    		int i = g[x][li];
    		if(i == fa[x]) continue;
    		fa[i] = x;
    		dfs(i,deep+1);
    		sz[x] += sz[i];
    		if(sz[i] > sz[son[x]]) son[x] = i;
    	}
    }
    
    void dfs2(int x,int tp){
    	top[x] = tp;
    	id[x] = ++cnt;
    	rk[cnt] = x;
    	if(son[x]) dfs2(son[x],tp),bot[x] = bot[son[x]];
    	else bot[x] = x;
    	for(int li=0;li<g[x].size();li++){
    		int i = g[x][li];
    		if(i != fa[x] && i != son[x])
    			dfs2(i,i);
    	}
    }
    
    int lca(int u,int v){
    	while(top[u] != top[v]){
    		if(depth[top[u]] < depth[top[v]]) swap(u,v);
    		u = fa[top[u]];
    	}
    	if(depth[u] < depth[v]) return u;
    	return v;
    }
    
    int main(){
    	ios::sync_with_stdio(false);
    	int n,m,root;
    	cin>>n>>m>>root;
    	for(int i=1;i<=n-1;i++){
    		int u,v;
    		cin>>u>>v;
    		g[u].push_back(v);
    		g[v].push_back(u);
    	}
    	dfs(root,1);
    	dfs2(root,root);
    	while(m--){
    		int u,v;
    		cin>>u>>v;
    		cout<<lca(u,v)<<endl;
    	}
    	return 0;
    }
    

    还有很多...吃不消了

    待补

  • 相关阅读:
    UNIX环境高级编程——信号说明列表
    [Fiddler]如何让Fiddler可以抓取https的请求
    [Cookie] Read Cookie and Pass in headers
    [Training Video
    [Training Video
    [Training Video
    [Training Video
    [Training Video
    [Training Video
    [Training Video
  • 原文地址:https://www.cnblogs.com/fisherss/p/12207332.html
Copyright © 2011-2022 走看看