• 题意
    • 有n个村庄,连成一条线,有三个操作
      • 操作一,将指定的一个村庄摧毁
      • 操作二,查询指定村庄所在的线段上有多少个没有被摧毁的连续的村庄(即连续区间的长度)
      • 操作三,将上次被摧毁的村庄重建
  • 区间合并
    • 看到这样的连续序列问题,第一个想到的应该是区间的左右合并,合并的关键在于理解线段树的区间
    • 实际上,可以将线段树理解为多个两区间合并的数据结构,也就是说,每次只合并两个区间,而我们只要能对两个区间进行操作并合理更新区间值,就能保证最后的结果正确
    • 这里参考https://blog.csdn.net/qq_33279781/article/details/77505858
    • 操作一和操作三是基础的单点更新。

       

    • 操作二的查询,需要查询连续区间的长度,建树的时候要对每个节点对应的区间[l, r]记录从l开始向右的最大连续区间,长度为prefix,从r开始向左的最大连续区间,长度为suffix,记录中间的最大连续区间,长度为medium。
    • push up操作。当前节点的medium值为左区间medium,右区间medium,左区间suffix和右区间prefix,三个值的最大者。prefix等于左区间prefix值,当左区间prefix值等于左区间长度时,说明左区间和右区间prefix相邻,当前prefix等于左区间prefix加右区间prefix。suffix同理。

       

    • 单点查询如何转换成区间查询?查询的时候,在整个区间缩小为一个点的过程中,判断目标点是否在当前区间的连续区间内,如果在,我们再查询该连续区间的端点。(相当于感染)这样就可以查找到目标点所在的连续区间。
    • 代码
      • #include <iostream>
        #include <algorithm>
        #include <cstring>
        #include <cstdio>
        #include <utility>
        #include <stack>
        using namespace std;
        const int maxn = 1e6+10;
        stack<int> s;
        struct Node {
        	int l;
        	int r;
        	int medium;
        	int prefix;
        	int suffix;
        } tree[maxn<<2];
        void pushup(int p, int len) {
        	int left_interval = (len - len/2);//左区间长度
        	int right_interval = len / 2;//右区间长度
        	if(tree[p<<1].prefix==left_interval)//左区间左连续等于左区间长度
        		tree[p].prefix = tree[p<<1].prefix+tree[p<<1|1].prefix;//p区间左连续延伸
        	else
        		tree[p].prefix = tree[p<<1].prefix;
        	if(tree[p<<1|1].suffix==right_interval)
        		tree[p].suffix = tree[p<<1].suffix+tree[p<<1|1].suffix;
        	else
        		tree[p].suffix = tree[p<<1|1].suffix;
        	tree[p].medium = max(tree[p<<1].medium, tree[p<<1|1].medium);
        	tree[p].medium = max(tree[p<<1].suffix+tree[p<<1|1].prefix, tree[p].medium);
        }
        void build(int p, int l, int r) {
        	tree[p].prefix = tree[p].suffix = tree[p].medium = r-l+1;
        	tree[p].l=l,tree[p].r=r;
        	if(l==r) {
        		return ;
        	}
        	int mid = (l+r)>>1;
        	build(p<<1, l, mid);
        	build(p<<1|1, mid+1, r);
        //	pushup(p, r-l+1);
        }
        void update(int p, int idx, int x) {
        	if(tree[p].l==tree[p].r && tree[p].l==idx) {
        		tree[p].medium = tree[p].prefix = tree[p].suffix = x;
        		return ;
        	}
        	int mid = (tree[p].l+tree[p].r)>>1;
        	if(idx<=mid) update(p<<1, idx, x);
        	if(idx>mid) update(p<<1|1, idx, x);
        	pushup(p, tree[p].r-tree[p].l+1);
        }
        int query(int p, int idx) {
        	if(tree[p].l==tree[p].r || tree[p].medium==0 ||tree[p].medium == (tree[p].r-tree[p].l+1)) {
        		return tree[p].medium;
        	}
        	int mid = (tree[p].l+tree[p].r)>>1;
        	if(idx<=mid) {
        		//如果idx在左区间的右连续内,则需要加上右区间的左连续 
        		if(idx>=mid-tree[p<<1].suffix+1) return query(p<<1, idx) + query(p<<1|1, mid+1);//这时候 
        		else return query(p<<1, idx);
        	}
        	if(mid<idx) {
        		//同理咯 
        		if(idx<=mid+1+tree[p<<1|1].prefix-1) return query(p<<1, mid) + query(p<<1|1, idx);
        		else return query(p<<1|1, idx);
        	}
        }
        int main() {
        	int n, q, x;
        	char opt[10];
        	while(scanf("%d%d", &n, &q)!=EOF) {
        		while(!s.empty()) s.pop();
        		build(1, 1, n);
        		for(int i=0; i<q; ++i) {
        			scanf("%s", opt);
        			if(opt[0]=='D') {
        				scanf("%d", &x);
        				update(1, x, 0);
        				s.push(x);
        			}
        			if(opt[0]=='Q') {
        				scanf("%d", &x);
        				printf("%d\n", query(1, x));
        			}
        			if(opt[0]=='R') {
        				if(!s.empty()) {
        					update(1, s.top(), 1);
        					s.pop();
        				}
        			}
        		}
        	}
        	return 0;
        }
        
  • 转化为区间最值
    • 参考https://blog.csdn.net/chudongfang2015/article/details/52133243
    • 这个思路类似于前一篇博客中的思路,两者都不是在区间种朴素地存储每个值,而是建立于区间整体之上的
    • 本题要求的是包含下标x的最长序列,而毁灭操作相当于赋值操作,所以我们将未毁灭的区间的最大值值赋为0,最小值赋为n+1,将“毁灭”操作转化为将下标为x的值转化为v,实际上可以转化成这样一个区间问题:
      • 求[x,n]中的最小值与[1,x]中的最大值的差-1的值
      • 当然,对于min==max的时候需要特判为0
    • 代码
      • #include<algorithm>
        #include <iostream>
        #include  <fstream>
        #include  <cstdlib>
        #include  <cstring>
        #include  <cassert>
        #include   <cstdio>
        #include   <vector>
        #include   <string>
        #include    <cmath>
        #include    <queue>
        #include    <stack>
        #include      <set>
        #include      <map>
        using namespace std;
        #define P(a,b,c) make_pair(a,make_pair(b,c))
        #define rep(i,a,n) for (int i=a;i<=n;i++)
        #define per(i,a,n) for (int i=n;i>=a;i--)
        #define CLR(vis) memset(vis,0,sizeof(vis))
        #define MST(vis,pos) memset(vis,pos,sizeof(vis))
        #define pb push_back
        #define mp make_pair
        #define all(x) (x).begin(),(x).end()
        #define fi first
        #define se second
        #define SZ(x) ((int)(x).size())
        typedef pair<int,pair<int,int> >pii;
        typedef long long ll;
        typedef unsigned long long ull;
        const ll mod = 1000000007;
        const int INF = 0x3f3f3f3f;
        ll gcd(ll a, ll b) {
        	return b ? gcd(b, a%b) : a;
        }
        template<class T>inline void gmax(T &A, T B) {
        	(A<B)&&(A=B);//if(B>A)A=B;
        }
        template<class T>inline void gmin(T &A, T B) {
        	(A>B)&&(A=B);//if(B<A)A=B;
        }
        template <class T>
        inline bool scan(T &ret) {
        	char c;
        	int sgn;
        	if(c=getchar(),c==EOF) return 0; //EOF
        	while(c!='-'&&(c<'0'||c>'9')) c=getchar();
        	sgn=(c=='-')?-1:1;
        	ret=(c=='-')?0:(c-'0');
        	while(c=getchar(),c>='0'&&c<='9') ret=ret*10+(c-'0');
        	ret*=sgn;
        	return 1;
        }
        inline void outt(int x) {
        	if(x>9) outt(x/10);
        	putchar(x%10+'0');
        }
        const int maxn=1e6+10;
        int a[maxn];
        struct SegmentTree {
        	int l,r;
        	int maxx;
        	int minn;
        } t[maxn * 4];
        int n; 
        void build(int p,int l,int r) { //build(1,1,n) 1为根节点
        	t[p].l=l,t[p].r=r;
        	if(l==r) {
        		t[p].maxx=0;
        		t[p].minn=n+1;
        		return;
        	}
        	int mid=(l+r)/2;
        	build(p*2,l,mid);
        	build(p*2+1,mid+1,r);
        	t[p].maxx=max(t[p*2].maxx,t[p*2+1].maxx);
        	t[p].minn=min(t[p*2].minn,t[p*2+1].minn);
        }
        void change_min(int p,int x,int v) { //change(1,x,v) 1为根节点
        	if(t[p].l==t[p].r) {
        		t[p].minn=v;
        		return ;
        	}
        	int mid=(t[p].l+t[p].r)/2;
        	if(x<=mid)change_min(p*2,x,v);
        	else change_min(p*2+1,x,v);
        	t[p].minn=min(t[p*2].minn,t[p*2+1].minn);
        }
        void change_max(int p,int x,int v) {
        	if(t[p].l==t[p].r) {
        		t[p].maxx=v;
        		return ;
        	}
        	int mid=(t[p].l+t[p].r)/2;
        	if(x<=mid)change_max(p*2,x,v);
        	else change_max(p*2+1,x,v);
        	t[p].maxx=max(t[p*2].maxx,t[p*2+1].maxx);
        }
        int ask_max(int p,int l,int r) { //ask(1,l,r) 1为根节点
        	if(l<=t[p].l && r>=t[p].r)return t[p].maxx;
        	int mid=(t[p].l + t[p].r)/2;
        	int val=-(1<<30);
        	if(l<=mid)val=max(val,ask_max(p*2,l,r));
        	if(r>mid) val=max(val,ask_max(p*2+1,l,r));
        	return val;
        }
        int ask_min(int p,int l,int r) { //ask(1,l,r) 1为根节点
        	if(l<=t[p].l && r>=t[p].r)return t[p].minn;
        	int mid=(t[p].l + t[p].r)/2;
        	int val=1<<30;
        	if(l<=mid)val=min(val,ask_min(p*2,l,r));
        	if(r>mid) val=min(val,ask_min(p*2+1,l,r));
        	return val;
        }
        int history[maxn];//保存历史,用于R
        int main() {
        	int m,a,temp,count;
        	char ch[10];
        	while(scanf("%d %d",&n,&m) == 2) {
        		count = 0;  //历史计算器
        		memset(history,0,sizeof(history));
        		build(1,1,n);
        		while(m--) {
        			scanf("%s",ch);
        			if(ch[0] == 'D') {
        				scanf("%d",&a);
        				change_min(1,a,a);//更新线段数的值,把a对应的值更新成a
        				change_max(1,a,a);
        				history[++count] = a;  //存储到历史记录中
        			} else if(ch[0] == 'Q') {
        				int re,max1,min1;
        				scanf("%d",&a);
        				max1=ask_max(1,1,a);//根据线段树查询
        				min1=ask_min(1,a,n);
        				if(max1 == min1)         //考虑特殊情况
        					printf("%d\n",0);
        				else
        					printf("%d\n",min1-max1-1);
        			} else {
        				temp = history[count--];
        				change_min(1,temp,n+1); //如果恢复,就把对应的值改为0或n+1
        				change_max(1,temp,0);
        			}
        		}
        	}
        	return 0;
        }
        

发表评论

邮箱地址不会被公开。 必填项已用*标注