CF915E Physical Education Lessons(动态开点线段树/珂朵莉树)

CF915E

洛谷链接

题目大意(懒狗直接偷的题面):

从现在到学期结束还有 \(n\) 天(从 \(1\)\(n\) 编号),他们一开始都是工作日。接下来学校的工作人员会依次发出 \(q\) 个指令,每个指令可以用三个参数 \(l,r,k\) 描述:

  • 如果 \(k=1\),那么从 \(l\)\(r\) (包含端点)的所有日子都变成工作日。
  • 如果 \(k=2\),那么从 \(l\)\(r\) (包含端点)的所有日子都变成工作日
    统计每个指令下发后,剩余的工作日天数。

输入格式:

第一行一个整数 \(n\),第二行一个整数 \(q\) \((1\le n\le 10^9,\;1\le q\le 3\cdot 10^5)\),分别是剩余的天数和指令的个数。

接下来 \(q\) 行,第 \(i\) 行有 \(3\) 个整数 \(l_i,r_i,k_i\),描述第 \(i\) 个指令 \((1\le l_i,r_i\le n,\;1\le k\le 2)\)

输出格式:

输出 \(q\) 行,第 \(i\) 行表示第 \(i\) 个指令被下发后剩余的工作日天数。

样例输入 #1

4
6
1 2 1
3 4 1
2 3 2
1 3 2
2 4 1
1 4 2

样例输出 #1

2
0
2
3
1
4

分析1:

看到这题会想到常规线段树区间覆盖,但是发现数据范围 \(n\le 10^9\),线段树需要开四倍空间会超出空间限制。
虽然如此,询问操作还是比较小的,说明提前建好的线段树某些区间几乎用不到,所以可利用动态开点,需要用到此区间的时候再将它建立。

代码1(线段树动态开点):

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 16e6+10;
inline int read() {
	char ch=getchar();
	int f=1,x=0;
	while(ch<'0'||ch>'9') {
		if(ch=='-')
			f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		x=x*10+ch-'0';
		ch=getchar();
	}
	return f*x;
}

int n,m,root=0;
int cnt;

struct T{
int l,r,val,lazy;
}t[MAXN];


int newnode(){
    cnt++;
    t[cnt].lazy=-1;
    return cnt;
}


void update(int p){
    t[p].val=t[t[p].l].val+t[t[p].r].val;
}


void pushdown(int l,int r,int p){
    if(l==r||t[p].lazy==-1){
        return;
    }
    if(!t[p].l){
        t[p].l=newnode();
    }
    if(!t[p].r){
        t[p].r=newnode();
    }
    int mid=(l+r)>>1;
    t[t[p].l].val=(mid-l+1)*t[p].lazy;
	t[t[p].r].val=(r-mid)*t[p].lazy;
	t[t[p].l].lazy=t[p].lazy;
    t[t[p].r].lazy=t[p].lazy;
	t[p].lazy=-1;
}

void change(int l,int r,int &p,int x,int y,int k){
	if(r<x||y<l) {
		return ;
	}
    if(!p) p=newnode();
    if(x<=l&&r<=y){
		t[p].lazy=k;
		t[p].val=(r-l+1)*k;
		return;
	}
    pushdown(l,r,p);
    int mid=(l+r)>>1;
    change(l,mid,t[p].l,x,y,k);
    change(mid+1,r,t[p].r,x,y,k);
    //update(p);
	t[p].val=t[t[p].l].val+t[t[p].r].val;
}

signed main(){
    n=read();
    m=read();
    change(1,n,root,1,n,1);
    for(int i=1;i<=m;i++){
        int l,r,op;
		l=read();
        r=read();
        op=read();

		if(op==1){
            change(1,n,root,l,r,0);
        }
        else{
            change(1,n,root,l,r,1);
        }
        printf("%d\n",t[1].val);
    }

    return 0;
}

代码1参考
线段树动态开点教学

分析2:

区间01赋值操作就是珂朵莉树的常规操作。
所以使用珂朵莉树就是一个较为模板的题。

代码2(珂朵莉树):

#include<bits/stdc++.h>
#define ll long long 
#define sit set<asd>::iterator
using namespace std;
inline ll read() {
     ll x = 0, fh = 1;
     char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            fh = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * fh;
}
 
struct asd{
ll l,r;
mutable ll val;
bool operator < (const asd& A)const{return l<A.l;}
asd(ll aa,ll bb,ll cc){l=aa,r=bb,val=cc;}
asd(ll aa){l=aa;}
};
 
int sum=0;
ll n,m,maxn,seed;
set<asd>s;
 
 
sit split(ll wz){
sit it=s.lower_bound(asd(wz));
if(it!=s.end()&&it->l==wz)return it;
it--;
ll l=it->l;
ll r=it->r;
ll val=it->val;
s.erase(it);
s.insert(asd(l,wz-1,val));
return s.insert(asd(wz,r,val)).first;
}
 
void assign(int l,int r,bool val){
   	sit itr = split(r+1), itl = split(l), it = itl;
    for( ;itl != itr; ++itl)
        sum-=itl->val*(itl->r-itl->l+1);
    s.erase(it,itr);
    s.insert(asd(l,r,val));
    sum+=val*(r-l+1);
}
 
 
signed main(){
    n=read();
    m=read();
 
	s.insert(asd(1,n,1));
    sum=n;
    while(m--){
        int l=read(),r=read(),op=read();
        if(op==1){
            assign(l,r,0);
        }
        else {
            assign(l,r,1);
        }
        printf("%d\n",sum);
    }
 
return 0;
}

珂朵莉树做法参考

posted @ 2022-09-13 16:20  DAIANZE  阅读(66)  评论(0编辑  收藏  举报