线段树暑假进阶

线段树

数学计算

这道题难在建模

线段树维护每次乘的数,然后除法就把当时乘的数变成\(1\) 复杂度 \(O(QlogQ)\)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N=1000005;
inline int read() {
	int x=0,f=1;char ch=getchar();
	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;
}
inline void Max(int &x,int y){if(x<y)x=y;}
inline void Min(int &x,int y){if(x>y)x=y;}
int n,mod,rt,cnt;
int ls[N],rs[N];
ll sum[N];

#define mid ((l+r)>>1)

void build(int l,int r,int &p) {
	p=++cnt;
	if(l==r) { sum[p]=1;return; }
	build(l,mid,ls[p]);
	build(mid+1,r,rs[p]);
	sum[p]=(sum[ls[p]]*sum[rs[p]])%mod;
} 

void modify(int l,int r,int pos,int v,int &p) {
	if(l==r) { sum[p]=v;return; }
	if(pos<=mid) modify(l,mid,pos,v,ls[p]);
	else modify(mid+1,r,pos,v,rs[p]);
	sum[p]=(sum[ls[p]]*sum[rs[p]])%mod;
}

ll query(int l,int r,int L,int R,int p) {
	if(L<=l&&r<=R) return sum[p];
	ll res=1;
	if(L<=mid) res=res*query(l,mid,L,R,ls[p])%mod;
	if(R>mid) res=res*query(mid+1,r,L,R,rs[p])%mod;
	return res;
}

int main() {
	int T=read();
	while(T--) {
		cnt=0;
		memset(sum,0,sizeof(sum));
		memset(ls,0,sizeof(ls));
		memset(rs,0,sizeof(rs));
		
		n=read();mod=read();
		build(1,n,rt);
		for(int i=1,op,pos;i<=n;i++) {
			op=read();pos=read();
			if(op==1) {
				modify(1,n,i,pos,rt);
				printf("%lld\n",query(1,n,1,n,rt));
			}
			else {
				modify(1,n,pos,1,rt);
				printf("%lld\n",query(1,n,1,n,rt));
			}
		}
	}
	
	return 0;
}

NOI2016 区间

Description

\(n\)个区间内选\(m\)个区间,使得m个区间共同包含一个点,花费为所选最长区间长 — 所选最短区间长度

Solution

­首先我们考虑暴力怎么做,按长度排序之后,我们容易发现,如果枚举一个区间作为左端点,一个区间作为右端点,那么我们就是求只在这个区间中选取的答案。

­我们把这一段的所有区间的对应的一段的经过次数都加一(区间加),最后只需\(𝑐ℎ𝑒𝑐𝑘\)一下这一段中是否出现了一个被经过\(𝑚\)次的点(区间查询),一旦存在就说明,我们一定可以找到其中的\(𝑚\)个区间满足题目的要求,所以我们就可以确保在这个区间中能够选取\(𝑚\)个区间并一定合法,就可以用右端点的那个区间长度 — 左端点的那个区间长度来更新答案。(并不关心具体选了哪些区间,中间有多余的也不影响答案)

­上述做法的复杂度可以用线段树维护来做到\(O(𝑛^2𝑙𝑜𝑔𝑛)\)。深入思考可以发现,其实右端点肯定是不降的,所以我们没必要再枚举一个右端点,只要用单调指针一直往后扫即可。总复杂度为:\(𝑂(𝑛𝑙𝑜𝑔𝑛)\)

血与泪的教训——没事别加那么多 inline ——MLE

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=5000010;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,m;
struct section{
    int l,r,len;
    bool operator < (const section &x)const{
        return len<x.len;
    }
}a[N*4];
int tot,b[N];
//           维护最大值
int tree_cnt,val[N],tag[N],ls[N],rs[N],root;
void pushup(int p){
    val[p]=max(val[ls[p]],val[rs[p]]);
}
void pushdown(int p){
    if(!tag[p]) return;
    val[ls[p]]+=tag[p];
    val[rs[p]]+=tag[p];
    tag[ls[p]]+=tag[p];
    tag[rs[p]]+=tag[p];
    tag[p]=0;
}
void build(int l,int r,int &p){
    p=++tree_cnt;
    if(l==r) return;
    int mid=(l+r)>>1;
    build(l,mid,ls[p]);
    build(mid+1,r,rs[p]);
}
void modify(int L,int R,int l,int r,int add,int p){
    if(L<=l&&r<=R){tag[p]+=add;val[p]+=add;return;}
    pushdown(p);
    int mid=(l+r)>>1;
    if(L<=mid) modify(L,R,l,mid,add,ls[p]);
    if(R>mid) modify(L,R,mid+1,r,add,rs[p]);
    pushup(p);
}

int ans=0x3f3f3f3f;
int main(){
    n=read();m=read();
    for(int i=1;i<=n;i++){
        b[++tot]=a[i].l=read();b[++tot]=a[i].r=read();
        a[i].len=a[i].r-a[i].l+1;
    }
    sort(b+1,b+1+tot);
    tot=unique(b+1,b+1+tot)-b-1;
    for(int i=1;i<=n;i++){
        a[i].l=lower_bound(b+1,b+1+tot,a[i].l)-b;
        a[i].r=lower_bound(b+1,b+1+tot,a[i].r)-b;
    }
    sort(a+1,a+1+n);

    build(1,tot,root);
    int h=1,t=0;
    while(val[root]<m&&++t<=n) 
        modify(a[t].l,a[t].r,1,tot,1,root);
    if(t==n+1) {
        puts("-1");return 0;
    }
    ans=min(ans,a[t].len-a[h].len);
    while(h<=n){
        modify(a[h].l,a[h].r,1,tot,-1,root);
        while(val[root]<m&&++t<=n)
            modify(a[t].l,a[t].r,1,tot,1,root);
        if(t==n+1) break;
        ans=min(ans,a[t].len-a[h+1].len);//注意 h+1
        h++;
    }
    printf("%d\n",ans);
    return 0;
}

HEOI 2012 采花

3e5的范围莫队水过

类比HH的项链,离线下来,按\(r\)排序,我们维护一个\(vis[i]\)表示\(i\)上次出现的位置,然后树状数组维护,每次\(upd(j,1),upd(vis[j],-1)\),然后计算再求和

这道题目要求只存在两个相同颜色,怎么办呢?

我们维护两个东西,\(last1[j]\)表示上上次出现j的位置,\(last2[j]\)表示上次出现的位置

对于第\(p\)个的数 \(j\)

第一次出现 \(j\) 时没有用,我们直接记录\(last1=p\);

第二次出现\(j\)时,他就会产生代价,但值得注意的是我们不应该在第二次出现\(j\)的位置上\(+1\),而是在上一次出现的位置\(last1\)\(+1\),这是因为我们按照\(r\)从小到大排序,比如\(2,2,3\)这个序列,如果我们在第二次出现\(2\)的位置上\(+1\),变成\((0,1,0)\),当询问\([2,3]\)就不对了,应该在倒数第二次出现的位置\(+1\)变成\((1,0,0)\);

两次以上出现,我们只需要在倒数第\(2\)次的位置上\(+1\),其他位置上出现\(j\)全部为零,实现:\(add(last1[j],-1);add(last2[j],1),last1=last2,last2=j;\)

#include <cstdio>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N=2e6+10;
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return f*x;
}
inline void Max(int &x,int y){if(x<y)x=y;}
inline void Min(int &x,int y){if(x>y)x=y;}
int n,m,num,c[N],bel[N];
struct node{
	int l,r,id;
	bool operator < (const node &x) const {
		return r<x.r;
	}
}q[N];
int t[N<<1];
inline void upd(int x,int v){for(;x<=n;x+=x&(-x))t[x]+=v;}
inline int sum(int x){int res=0;for(;x;x-=x&(-x))res+=t[x];return res;}
int ans[N],res;
int pre1[N],pre2[N];

int main() {
	n=read();num=read();m=read();
	for(int i=1;i<=n;i++) c[i]=read();
	for(int i=1;i<=m;i++) 
		q[i].l=read(),q[i].r=read(),q[i].id=i;
	sort(q+1,q+1+m);
	int nxt=1;
	for(int i=1;i<=m;i++) {
		for(int j=nxt;j<=q[i].r;j++) {
			if(pre2[c[j]]) {
				upd(pre1[c[j]],-1);
				upd(pre2[c[j]],1);
				pre1[c[j]]=pre2[c[j]];pre2[c[j]]=j;
			}
			else if(pre1[c[j]]&&!pre2[c[j]]) {
				upd(pre1[c[j]],1);
				pre2[c[j]]=j;
			}
			else if(!pre1[c[j]]) {
				pre1[c[j]]=j;
			}
		}
		nxt=q[i].r+1;
		ans[q[i].id]=sum(q[i].r)-sum(q[i].l-1);
	}
	for(int i=1;i<=m;i++)
		printf("%d\n",ans[i]);
	return 0;
}

card

线段树维护图的联通性

不妨假设\(a_i<b_i\)(不满足交换),线段树维护区间左端点取\(a_i/b_i\),右端点最小是多少,修改时相当于两次单点修改(注意修改是永久的)

#include<bits/stdc++.h>
using namespace std;
#define N 200005
#define oo 0x3f3f3f3f
#define L (p<<1)
#define R (L+1)
#define mid (l+r>>1)
int n,m,x,y,a[N][2],f[N<<2][2];
void update(int p,int l,int r,int x){
    if (l==r){
        f[p][0]=a[l][0];
        f[p][1]=a[r][1];
        return;
    }
    if (x<=mid)update(L,l,mid,x);
    else update(R,mid+1,r,x);
    f[p][0]=f[p][1]=oo;
    for(int i=0;i<2;i++)
        for(int j=1;j>=0;j--)
            if (f[L][i]<=a[mid+1][j])f[p][i]=f[R][j];
}
int main(){
    scanf("%d",&n);
    memset(f,oo,sizeof(f));
    a[n+1][0]=a[n+1][1]=oo-1;
    for(int i=1;i<=n;i++){
        scanf("%d%d",&a[i][0],&a[i][1]);
        if (a[i][0]>a[i][1])swap(a[i][0],a[i][1]);
        update(1,1,n,i);
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        swap(a[x][0],a[y][0]);
        swap(a[x][1],a[y][1]);
        update(1,1,n,x);
        update(1,1,n,y);
        if (f[1][0]==oo)printf("NIE\n");
        else printf("TAK\n");
    }
}	

SHOI2008 堵塞的交通——wait

6种情况 以每一列为一个叶子节点。

左上右下、左上右上、左下右下、左下右上、左上左下,右上右下

修改横向相邻的道路,所以我们还要维护一个side[]表示这个节点是否可以向外延伸。

https://www.cnblogs.com/SiriusRen/p/6536953.html

一脸不可做——下面是std——有空再说

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100100;
struct S{bool luru,ldrd,lurd,ldru,luld,rurd,side[3];}tr[N*4];
char ch[10];
int n,r1,c1,r2,c2;
#define mid (l+r)/2
#define L k<<1,l,mid
#define R k<<1|1,mid+1,r
S update(S x,S y)
{
    S ans;
    ans.side[1]=y.side[1];
    ans.side[2]=y.side[2];
    ans.luru=ans.ldrd=ans.lurd=ans.ldru=ans.luld=ans.rurd=false;
    if((x.luru&&x.side[1]&&y.luru)||(x.lurd&&x.side[2]&&y.ldru)) ans.luru=true;
    if((x.ldrd&&x.side[2]&&y.ldrd)||(x.ldru&&x.side[1]&&y.lurd)) ans.ldrd=true;
    if((x.luru&&x.side[1]&&y.lurd)||(x.lurd&&x.side[2]&&y.ldrd)) ans.lurd=true;
    if((x.ldrd&&x.side[2]&&y.ldru)||(x.ldru&&x.side[1]&&y.luru)) ans.ldru=true;
    if((x.luld)||(x.luru&&x.side[1]&&y.luld&&x.side[2]&&x.ldrd)) ans.luld=true;
    if((y.rurd)||(y.luru&&x.side[1]&&x.rurd&&x.side[2]&&y.ldrd)) ans.rurd=true;
    return ans;
}
void build(int k,int l,int r)
{
    if(l==r){
        tr[k].luru=tr[k].ldrd=true;
        return ;
    }
    build(L);build(R);
    tr[k]=update(tr[k<<1],tr[k<<1|1]);
}
void insert_a(int k,int l,int r,int x,bool kind)
{
    if(l==r){
        tr[k].luld=tr[k].rurd=tr[k].lurd=tr[k].ldru=kind;
        return ;
    }
    if(x<=mid) insert_a(L,x,kind);
    else insert_a(R,x,kind);
    tr[k]=update(tr[k<<1],tr[k<<1|1]);
}
void insert_b(int k,int l,int r,int x,int y,bool kind)
{
    if(l==r){
        tr[k].side[y]=kind;
        return ;
    }
    if(x<=mid) insert_b(L,x,y,kind);
    else insert_b(R,x,y,kind);
    tr[k]=update(tr[k<<1],tr[k<<1|1]);
}
S query(int k,int l,int r,int x,int y)
{
    S ans1,ans2;
    bool left=false,right=false;
    if(x<=l&&y>=r) return tr[k];
    if(x<=mid) ans1=query(L,x,y),left=true;
    if(y>mid) ans2=query(R,x,y),right=true;
    if(right&&left) return update(ans1,ans2);
    else return left?ans1:ans2;
}
bool check()
{
    S now,pre,last;
    if(c1>c2){
        swap(r1,r2);
        swap(c1,c2);
    }
    now=query(1,1,n,c1,c2);
    pre=query(1,1,n,1,c1);
    last=query(1,1,n,c2,n);
    if(r1==r2){
        if((r1==1)&&((now.luru)||(last.luld&&now.lurd)||(pre.rurd&&now.ldru)||(pre.rurd&&now.ldrd&&last.luld))) return true;
        if((r1==2)&&((now.ldrd)||(last.luld&&now.ldru)||(pre.rurd&&now.lurd)||(pre.rurd&&now.luru&&last.luld))) return true;
    }
    else{
        if((r1==1)&&((now.lurd)||(last.luld&&now.luru)||(pre.rurd&&now.ldrd)||(pre.rurd&&now.ldru&&last.luld))) return true;
        if((r1==2)&&((now.ldru)||(last.luld&&now.ldrd)||(pre.rurd&&now.luru)||(pre.rurd&&now.lurd&&last.luld))) return true;
    }
    return false;
}
int main()
{
    int i,j;
    scanf("%d",&n);
    build(1,1,n);
    while(scanf("%*c%s",&ch)){
        if(ch[0]=='E') break;
        scanf("%d%d%d%d",&r1,&c1,&r2,&c2);
        if(ch[0]=='O'){
            if(c1==c2) insert_a(1,1,n,c1,1);
            else insert_b(1,1,n,min(c1,c2),r1,1);
        }
        if(ch[0]=='C'){
            if(c1==c2) insert_a(1,1,n,c1,0);
            else insert_b(1,1,n,min(c1,c2),r1,0);
        }
        if(ch[0]=='A'){
            if(check()) printf("Y\n");
            else printf("N\n");
        }
    }
}

大都市MEG-Megalopolis

https://www.luogu.com.cn/problem/P3459

线段树上二分

还有一道考试题——https://www.cnblogs.com/ke-xin/p/13843379.html

按照生长速度a[]排序后,容易发现数列永远单调不降。

也就是说,每次砍完生长速度快的可能等于生长速度慢的,但之后立马又长得比他高。

所以每次操作都是一段连续的区间,可以线段树维护

1.最后一棵草的高度a

2.上次收割日期b

3.总的高度和c

4.总的生长速度和d

5.高度覆盖标记和时间标记

对于一次操作$$ x_i,y_i$$ 在线段树上二分找到第一个大于等于$$ x_i$$的位置$$ pos$$,然后将 $$[pos,n]$$全部覆盖为$$ y_i$$,顺便在覆盖的同时计算改变量即为答案。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <utility>
#include <algorithm>

using namespace std;

// typedef long long int;
#define int long long
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
const int N=1e6+10;
const int inf=1e9;

int n,m,a[N];
int sum[N];

#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
struct node
{
	int tim,mx,sum,tag;
}t[N*4];

void build(int l,int r,int p) {
	t[p].tag=-1;
	t[p].tim=t[p].mx=t[p].sum=0;
	if(l==r) return;
	build(l,mid,ls);
	build(mid+1,r,rs);
}

void modify(int l,int r,int v,int p) {
	t[p].tim+=v;
	t[p].mx+=a[r]*v;
	t[p].sum+=(sum[r]-sum[l-1])*v;
}

inline void pushdown(int p,int l,int r) {
	if(t[p].tag!=-1) {
		t[ls].mx=t[rs].mx=t[p].tag;
		t[ls].tag=t[rs].tag=t[p].tag;
		t[ls].sum=1ll*t[p].tag*(mid-l+1);
		t[rs].sum=1ll*t[p].tag*(r-mid);
		t[ls].tim=t[rs].tim=0;
		t[p].tag=-1;
	}
	if(t[p].tim) {
		modify(l,mid,t[p].tim,ls);modify(mid+1,r,t[p].tim,rs);
		t[p].tim=0;
	}
}

inline int find(int l,int r,int u,int p) {
	if(l==r) return l;
	pushdown(p,l,r);
	if(u<=t[ls].mx) return find(l,mid,u,ls);
	else return find(mid+1,r,u,rs);
}

inline int query(int l,int r,int L,int R,int p) {
	if(L<=l&&r<=R) return t[p].sum;
	pushdown(p,l,r);
	int ans=0;
	if(L<=mid) ans+=query(l,mid,L,R,ls);
	if(R>mid) ans+=query(mid+1,r,L,R,rs);
	return ans;
}

inline void cover(int l,int r,int L,int R,int x,int p) {
	if(L<=l&&r<=R) {
		t[p].tag=x;t[p].mx=x;t[p].sum=(r-l+1)*x;t[p].tim=0;
		return;
	}
	pushdown(p,l,r);
	if(L<=mid) cover(l,mid,L,R,x,ls);
	if(R>mid) cover(mid+1,r,L,R,x,rs);
	t[p].sum=t[ls].sum+t[rs].sum;
	t[p].mx=t[rs].mx;
}
signed main() {
	// freopen("1.in","r",stdin);
	// freopen("1.out","w",stdout);
	n=read();m=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	sort(a+1,a+1+n);
	for(int i=1;i<=n;i++)
		sum[i]=sum[i-1]+a[i];
	build(1,n,1);
	int last=0;
	while(m--) {
		int d=read(),b=read();
		modify(1,n,d-last,1);//修改时间
		last=d;
		int pos=find(1,n,b,1);
		if(t[1].mx<b) {
			puts("0");continue;
		}
		int ans=query(1,n,pos,n,1);
		printf("%lld\n",ans-(b*(n-pos+1)));
		cover(1,n,pos,n,b,1);
	}
	return 0;	
}
posted @ 2020-10-20 09:59  ke_xin  阅读(39)  评论(0编辑  收藏  举报