1. 程式人生 > >線段樹合集

線段樹合集


POJ - 2528 - Mayor's posters

題目連結<http://poj.org/problem?id=2528>


題意:

在範圍為1e9的瓷磚上,依次覆蓋1e4張海報,每張海報各不相同,問最後能看到的海報有多少張。


題解:

離散加區間修改和最後一次的區間查詢。

離散有兩種考慮方式:

  1. 離散邊界,也就是說對於瓷磚區間r+1或者l-1。
  2. 離散區間,這樣容易忽略離散後的順序相鄰但實際的位置並不相鄰的情況,可以考慮在每個位置不相鄰的節點中間另外加一個節點即可。

#include<cstdio>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const int N=1e6+7;
int lazy[N<<2];
int x[N],y[N];
int a[N*2];
bool vis[N*2];
int ans=0;
void pd(int rt){
    if(lazy[rt]!=-1){
        lazy[rt<<1]=lazy[rt<<1|1]=lazy[rt];
        lazy[rt]=-1;
    }
}
void update(int L,int R,int l,int r,int rt,int tp){
    if(L<=l&&r<=R){
        lazy[rt]=tp;
        return;
    }
    pd(rt);
    int m=l+r>>1;
    if(L<=m) update(L,R,l,m,rt<<1,tp);
    if(m<R) update(L,R,m+1,r,rt<<1|1,tp);
}

void query(int l,int r,int rt){
    if(lazy[rt]!=-1){
        if(!vis[lazy[rt]]) ans++;
        vis[lazy[rt]]=true;
        return;
    }
    if(l==r)return;
    int m=l+r>>1;
    query(l,m,rt<<1);
    query(m+1,r,rt<<1|1);
}

int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--){
        memset(vis,false,sizeof(vis));
        memset(lazy,-1,sizeof(lazy));
        scanf("%d",&n);
        ans=0;
        int tot=0;
        for(int i=1;i<=n;i++){
            scanf("%d%d",&x[i],&y[i]);
            y[i]+=1;
            a[++tot]=x[i];
            a[++tot]=y[i];
        }
        sort(a+1,a+1+tot);
        tot=unique(a+1,a+1+tot)-a-1;
        /*
        int tt=tot;
        for(int i=2;i<=tot;i++){
            if(a[i]>a[i-1]+1) a[++tt]=a[i-1]+1;
        }
        tot=tt;
        sort(a+1,a+1+tot);
        for(int i=1;i<=n;i++){
            x[i]=lower_bound(a+1,a+1+tot,x[i])-a;
            y[i]=lower_bound(a+1,a+1+tot,y[i])-a;
            update(x[i],y[i],1,tot,1,i);
        }
        */
        for(int i=1;i<=n;i++){
            x[i]=lower_bound(a+1,a+1+tot,x[i])-a;
            y[i]=lower_bound(a+1,a+1+tot,y[i])-a;
            update(x[i],y[i]-1,1,tot,1,i);
        }
        query(1,tot,1);
        printf("%d\n",ans);
    }
}

HDU - 4027 - Can you answer these queries?

題目連結<http://acm.hdu.edu.cn/showproblem.php?pid=4027>


題意:

區間修改:區間內的每個數變成原來數字的平方根。

區間查詢:查詢區間和。

數字的範圍小於2^{63}


題解:

對於範圍在2^{63}內的,最多做7此平方根。所以線段樹每個節點最多隻需要做7次的修改。記錄每個區間的修改次數即可。


#include<cstdio>
#include<algorithm>
#include<string.h>
#include<cmath>
using namespace std;
typedef unsigned long long ll;
const int N=1e5+7;
ll sum[N<<2];
int num[N<<2];
void pu(int rt){
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt){
    if(l==r){
        scanf("%llu",&sum[rt]);
        return;
    }
    int m=l+r>>1;
    build(l,m,rt<<1);
    build(m+1,r,rt<<1|1);
    pu(rt);
}
void update(int L,int R,int l,int r,int rt){
    if(L<=l&&r<=R){
        if(num[rt]>=7){
            sum[rt]=r-l+1;
            return;
        }
        num[rt]++;
    }
    if(l==r){sum[rt]=(ll)sqrt(sum[rt]);return;}
    int m=r+l>>1;
    if(L<=m) update(L,R,l,m,rt<<1);
    if(m<R) update(L,R,m+1,r,rt<<1|1);
    pu(rt);
}
ll query(int L,int R,int l,int r,int rt){
    if(L<=l&&r<=R) return sum[rt];
    int m=r+l>>1;
    ll res=0;
    if(L<=m) res+=query(L,R,l,m,rt<<1);
    if(m<R) res+=query(L,R,m+1,r,rt<<1|1);
    return res;
}
int main()
{
    int n,q;
    int cs=0;
    while(scanf("%d",&n)!=EOF){
        printf("Case #%d:\n",++cs);
        build(1,n,1);
        memset(num,0,sizeof(num));
        scanf("%d",&q);
        int a,b,c;
        while(q--){
            scanf("%d%d%d",&a,&b,&c);
            if(b>c) swap(b,c);
            if(a==0) update(b,c,1,n,1);
            else printf("%llu\n",query(b,c,1,n,1));
        }
        printf("\n");
    }
}

HDU - 1540 - Tunnel Warfare

題目連結<http://acm.hdu.edu.cn/showproblem.php?pid=1540>


題意:

一共有三種操作:破壞一個村莊,修復上一次毀壞的村莊,查詢包括村莊x且沒有被毀壞的最大區間。


題解:

有兩種方案。

第一種可以二分村莊左邊和右邊最大的範圍,線上段樹上查詢。複雜度在log*log級別。

第二種不需要二分,線段樹維護兩個東西:區間左端點向右的最大範圍,區間右端點向左的最大範圍。

如果一個區間的左端點範圍能覆蓋x,那麼答案是這個範圍加上同一層左邊區間右端點的範圍,反之同理。

#include<bits/stdc++.h>
using namespace std;
const int N=5e4+7;
int n,m,x;
int ll[N<<2],rl[N<<2],len[N<<2];
char s[5];
int pu(int rt){
    ll[rt]=ll[rt<<1];
    if(ll[rt<<1]==len[rt<<1]) ll[rt]+=ll[rt<<1|1];
    rl[rt]=rl[rt<<1|1];
    if(rl[rt<<1|1]==len[rt<<1|1]) rl[rt]+=rl[rt<<1];
    len[rt]=len[rt<<1]+len[rt<<1|1];
}
void build(int l,int r,int rt){
    if(l==r){
        ll[rt]=rl[rt]=len[rt]=1;
        return;
    }
    int m=l+r>>1;
    build(l,m,rt<<1);
    build(m+1,r,rt<<1|1);
    pu(rt);
}
void update(int x,int l,int r,int rt,int val){
    if(l==r&&l==x){
        ll[rt]=rl[rt]=val;
        return;
    }
    int m=l+r>>1;
    if(x<=m) update(x,l,m,rt<<1,val);
    else update(x,m+1,r,rt<<1|1,val);
    pu(rt);
}
int query(int x,int l,int r,int rt){
    if(l==r) return ll[rt];
    if(l+ll[rt]-1>=x){
        if(l==1) return ll[rt];
        else return ll[rt]+rl[rt-1];
    }
    if(r-rl[rt]+1<=x){
        if(r==n) return rl[rt];
        else return rl[rt]+ll[rt+1];
    }
    int m=l+r>>1;
    if(x<=m) return query(x,l,m,rt<<1);
    else return query(x,m+1,r,rt<<1|1);
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF){
        stack<int>st;
        build(1,n,1);
        while(m--){
            scanf("%s",s);
            if(s[0]=='D'){
                scanf("%d",&x);
                st.push(x);
                update(x,1,n,1,0);
            }
            else if(s[0]=='R'){
                x=st.top();st.pop();
                update(x,1,n,1,1);
            }
            else{
                scanf("%d",&x);
                printf("%d\n",query(x,1,n,1));
            }
        }
    }
}

HDU - 3974 - Assign the task

題目連結<http://acm.hdu.edu.cn/showproblem.php?pid=3974>


題意:

n個員工組成一顆單向的樹,每次分派給一個員工一個任務,他以及在他下面直接或間接相連的所有員工都得做這個任務。詢問是哪個員工當前做的任務是什麼。初始化都是-1。


題解:

對映+線段樹。

每一個員工能確定一個範圍,每修改一個員工就是一次區間修改。

每一個員工也有對應的一個葉子節點,每次查詢就是單點查詢。

葉子節點的順序可以看成dfs序。


#include<bits/stdc++.h>
using namespace std;
const int N=5e4+7;
struct Edge{
    int v,nxt;
    Edge(int v=0,int nxt=0):v(v),nxt(nxt){}
}e[N];
struct Node{
    int l,r;
}dm[N];
int p[N],edn;
void add(int u,int v){
    e[++edn]=Edge(v,p[u]);p[u]=edn;
}
bool vis[N];
int mp[N];
int n,m,t,cs=0;
int cnt=0;
void init(int u){
    cnt++;
    mp[u]=dm[u].l=cnt;
    if(p[u]==-1){
        dm[u].r=cnt;
        return;
    }
    for(int i=p[u];~i;i=e[i].nxt){
        int v=e[i].v;
        init(v);
        dm[u].r=dm[v].r;
    }
}
int lazy[N<<2];
void pd(int rt){
    if(~lazy[rt]){
        lazy[rt<<1]=lazy[rt<<1|1]=lazy[rt];
        lazy[rt]=-1;
    }
}
void update(int L,int R,int l,int r,int rt,int val){
    if(L<=l&&r<=R){
        lazy[rt]=val;
        return;
    }
    pd(rt);
    int m=l+r>>1;
    if(L<=m) update(L,R,l,m,rt<<1,val);
    if(m<R) update(L,R,m+1,r,rt<<1|1,val);
}
int query(int x,int l,int r,int rt){
    if(~lazy[rt]||l==r){
        return lazy[rt];
    }
    int m=l+r>>1;
    if(x<=m) return query(x,l,m,rt<<1);
    else return query(x,m+1,r,rt<<1|1);
}
int main()
{
    scanf("%d",&t);
    while(t--){
        memset(vis,false,sizeof(vis));
        memset(p,-1,sizeof(p));edn=-1;
        memset(lazy,-1,sizeof(lazy));
        cnt=0;
        scanf("%d",&n);
        int u,v;
        for(int i=1;i<n;i++){
            scanf("%d%d",&v,&u);
            add(u,v);vis[v]=true;
        }
        for(int i=1;i<=n;i++){
            if(!vis[i]){
                init(i);
                break;
            }
        }
        scanf("%d",&m);
        printf("Case #%d:\n",++cs);
        char s[5];
        while(m--){
            scanf("%s",s);
            if(s[0]=='C'){
                scanf("%d",&u);
                printf("%d\n",query(mp[u],1,n,1));
            }
            else{
                scanf("%d%d",&u,&v);
                update(dm[u].l,dm[u].r,1,n,1,v);
            }
        }
    }
}

【多種修改】HDU - 4578 - Transformation

題目連結<http://acm.hdu.edu.cn/showproblem.php?pid=4578>


題意:

對於一串數字,初始化為零,一共有四種操作:

  1. 對一段區間加上一個值。
  2. 對一段區間乘上一個值。
  3. 把一段區間內每個數字變成同一個值。
  4. 對一段區間查詢a_{x}^{p}+a_{x+1}^{p}+...+a_{y}^{p}, (1\leq p\leq 3)

題解:

首先考慮查詢,因為p的值只有三種,可以對每種都進行維護。

乘上一個值以及變成一個值都很好維護,加法可以拆開來推:

  • 平方和:(a+c)^{2}=a^2+2ac+c^2
  • 立方和:(a+c)^{3}=a^3+c^3+3a^2c+3ac^{2}

還有一個問題,這三種操作之間會互相影響,不能簡單的分開維護。可以這麼考慮:(a+b)*c=a*c+b*c,也就是說每乘上一個數,區間上add陣列也要乘上mul的值。一旦執行第三個操作,那麼add和mul都可以迴歸初始化。這樣子的話pushdown數組裡的順序也很明顯是3->2->1。


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+7;
const int mod=1e4+7;
int p[4][N<<2];
int add[N<<2],mul[N<<2],chg[N<<2];

void ad(int l,int r,int rt,int c){
    p[3][rt]=(p[3][rt]+3*c*p[2][rt]%mod+3*c*c%mod*p[1][rt]%mod+c*c%mod*c%mod*(r-l+1)%mod)%mod;
    p[2][rt]=(p[2][rt]+2*c*p[1][rt]%mod+c*c%mod*(r-l+1)%mod)%mod;
    p[1][rt]=(p[1][rt]+c*(r-l+1)%mod)%mod;
}
void mu(int rt,int c){
    p[1][rt]=p[1][rt]*c%mod;
    p[2][rt]=p[2][rt]*c%mod*c%mod;
    p[3][rt]=p[3][rt]*c%mod*c%mod*c%mod;
}
void cg(int l,int r,int rt,int c){
    p[1][rt]=c*(r-l+1)%mod;
    p[2][rt]=c*p[1][rt]%mod;
    p[3][rt]=c*p[2][rt]%mod;
}
void pd(int l,int r,int rt){
    int m=l+r>>1;
    if(chg[rt]){
        cg(l,m,rt<<1,chg[rt]);
        cg(m+1,r,rt<<1|1,chg[rt]);
        add[rt<<1]=0;mul[rt<<1]=1;
        add[rt<<1|1]=0;mul[rt<<1|1]=1;
        chg[rt<<1]=chg[rt<<1|1]=chg[rt];
        chg[rt]=0;
    }
    if(mul[rt]>1){
        mu(rt<<1,mul[rt]);
        mu(rt<<1|1,mul[rt]);
        add[rt<<1]=add[rt<<1]*mul[rt]%mod;
        mul[rt<<1]=mul[rt<<1]*mul[rt]%mod;
        add[rt<<1|1]=add[rt<<1|1]*mul[rt]%mod;
        mul[rt<<1|1]=mul[rt<<1|1]*mul[rt]%mod;
        mul[rt]=1;
    }
    if(add[rt]){
        ad(l,m,rt<<1,add[rt]);
        ad(m+1,r,rt<<1|1,add[rt]);
        add[rt<<1]=(add[rt<<1]+add[rt])%mod;
        add[rt<<1|1]=(add[rt<<1|1]+add[rt])%mod;
        add[rt]=0;
    }
}
void pu(int rt){
    p[1][rt]=(p[1][rt<<1]+p[1][rt<<1|1])%mod;
    p[2][rt]=(p[2][rt<<1]+p[2][rt<<1|1])%mod;
    p[3][rt]=(p[3][rt<<1]+p[3][rt<<1|1])%mod;
}
void build(int l,int r,int rt){
    add[rt]=0;mul[rt]=1;chg[rt]=0;
    p[1][rt]=p[2][rt]=p[3][rt]=0;
    if(l==r) return;
    int m=l+r>>1;
    build(l,m,rt<<1);
    build(m+1,r,rt<<1|1);
}
void update(int L,int R,int l,int r,int rt,int tp,int c){
    if(L<=l&&r<=R){
        if(tp==1){
            ad(l,r,rt,c);
            add[rt]=(add[rt]+c)%mod;
        }
        else if(tp==2){
            mu(rt,c);
            add[rt]=add[rt]*c%mod;
            mul[rt]=mul[rt]*c%mod;
        }
        else{
            cg(l,r,rt,c);
            add[rt]=0;mul[rt]=1;
            chg[rt]=c;
        }
        return;
    }
    pd(l,r,rt);
    int m=l+r>>1;
    if(L<=m) update(L,R,l,m,rt<<1,tp,c);
    if(m<R) update(L,R,m+1,r,rt<<1|1,tp,c);
    pu(rt);
}
int query(int L,int R,int l,int r,int rt,int c){
    if(L<=l&&r<=R) return p[c][rt];
    pd(l,r,rt);
    int m=l+r>>1,res=0;
    if(L<=m) res+=query(L,R,l,m,rt<<1,c);
    if(m<R) res+=query(L,R,m+1,r,rt<<1|1,c);
    return res%mod;
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF){
        if(n==0&&m==0) break;
        build(1,n,1);
        int tp,x,y,c;
        while(m--){
            scanf("%d%d%d%d",&tp,&x,&y,&c);
            if(tp<4) update(x,y,1,n,1,tp,c);
            else printf("%d\n",query(x,y,1,n,1,c));
        }
    }
}

HDU - 4614 - Vases and Flowers

題目連結<https://cn.vjudge.net/problem/45647/origin>


題意:

一共有兩個操作:

  1.  從第A個位置開始放F朵花,有空位就放。直到放完或者沒地方放為止。如果一朵花都沒有放就輸出“Can not put any one.”,否則輸出第一朵放花的位置和最後一朵放花的位置。
  2. 把一段區間的花朵清空。

題解:

如果能夠放完,那麼就二分可以放花的區間。利用線段樹查詢確定第一處放花和最後一處放花的位置。


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+7;
int sum[N<<2],lz[N<<2];
void pu(int rt){
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void pd(int l,int r,int rt){
    if(lz[rt]==1){
        int m=l+r>>1;
        sum[rt<<1]=m-l+1;
        sum[rt<<1|1]=r-m;
        lz[rt<<1]=lz[rt<<1|1]=1;
        lz[rt]=-1;
    }
    else if(lz[rt]==0){
        sum[rt<<1]=sum[rt<<1|1]=0;
        lz[rt<<1]=lz[rt<<1|1]=0;
        lz[rt]=-1;
    }
}

int q(int L,int R,int l,int r,int rt,int flag){
    if(L<=l&&r<=R){
        int res=sum[rt];
        if(flag==1) sum[rt]=r-l+1,lz[rt]=1;
        else if(flag==0) sum[rt]=0,lz[rt]=0;
        return res;
    }
    pd(l,r,rt);
    int m=l+r>>1,res=0;
    if(L<=m) res+=q(L,R,l,m,rt<<1,flag);
    if(m<R) res+=q(L,R,m+1,r,rt<<1|1,flag);
    pu(rt);
    return res;
}
int fdl(int l,int r,int rt,int x){
    if(l==r) return l;
    pd(l,r,rt);
    int m=l+r>>1;
    if(sum[rt<<1]<m-l+1&&x<=m){
    	int res=fdl(l,m,rt<<1,x);
    	if(res!=-1) return res;
    } 
    if(sum[rt<<1|1]<r-m) 
		return fdl(m+1,r,rt<<1|1,x);
    return -1;
}
int fdr(int l,int r,int rt){
    if(l==r) return l;
    pd(l,r,rt);
    int m=l+r>>1;
    if(sum[rt<<1|1]<r-m) return fdr(m+1,r,rt<<1|1);
    return fdr(l,m,rt<<1);
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);n--;
        memset(sum,0,sizeof(sum));
        memset(lz,-1,sizeof(lz));
        int k,x,y;
        while(m--){
            scanf("%d%d%d",&k,&x,&y);
            if(k==1){
                int ll=fdl(0,n,1,x);
                if(ll==-1){
                    printf("Can not put any one.\n");
                    continue;
                }
                int lo=x,hi=n,rr=-1;
                while(lo<=hi){
                    int mid=lo+hi>>1;
                    int num=mid-x+1-q(x,mid,0,n,1,-1);
                    if(num>=y) rr=mid,hi=mid-1;
                    else lo=mid+1;
                }
                if(rr==-1) rr=fdr(0,n,1);
                printf("%d %d\n",ll,rr);
                q(ll,rr,0,n,1,1);
            }
            else printf("%d\n",q(x,y,0,n,1,0));
        }
        printf("\n");
    }
}

HDU - 4553 - 約會安排

題目連結<http://acm.hdu.edu.cn/showproblem.php?pid=4553>


題解:

區間合併。建立兩個線段樹,一個線段樹記錄屌絲和女神的區間,一個只記錄女神的區間。

線段樹維護三個值:左端點向右的最大空間,右端點向左的最大空間,這個區間內的最大空間。lazy標記記錄這段區間是不是空的。


#include<bits/stdc++.h>
#define ll long long
#define lc rt<<1
#define rc rt<<1|1
using namespace std;
const int N=2e5+7;
int T,cs;
char s[10];
int n,m;
struct Node{
    int l,r,len;
    int lz;
}a[2][N<<2];
void build(int l,int r,int rt){
    a[0][rt].l=a[0][rt].r=a[0][rt].len=r-l+1;
    a[1][rt].l=a[1][rt].r=a[1][rt].len=r-l+1;
    a[0][rt].lz=a[1][rt].lz=-1;
    if(l==r) return;
    int m=l+r>>1;
    build(l,m,lc);
    build(m+1,r,rc);
}
void pd(int l,int r,int rt,int o){
    if(a[o][rt].lz==-1) return;
    int tmp;
    if(a[o][rt].lz==1){
        int m=l+r>>1;
        a[o][lc].l=a[o][lc].r=a[o][lc].len=m-l+1;
        a[o][rc].l=a[o][rc].r=a[o][rc].len=r-m;
        a[o][lc].lz=a[o][rc].lz=1;
        a[o][rt].lz=-1;
    }
    else if(a[o][rt].lz==0){
        a[o][lc].l=a[o][lc].r=a[o][lc].len=0;
        a[o][rc].l=a[o][rc].r=a[o][rc].len=0;
        a[o][lc].lz=a[o][rc].lz=0;
        a[o][rt].lz=-1;
    }
}
void pu(int l,int r,int rt,int o){
    int m=l+r>>1;
    a[o][rt].l=a[o][lc].l;
    a[o][rt].r=a[o][rc].r;
    if(a[o][rt].l>=m-l+1) a[o][rt].l+=a[o][rc].l;
    if(a[o][rt].r>=r-m) a[o][rt].r+=a[o][lc].r;
    a[o][rt].len=max(a[o][lc].len,a[o][rc].len);
    a[o][rt].len=max(a[o][rt].len,max(a[o][rt].l,a[o][rt].r));
    a[o][rt].len=max(a[o][rt].len,a[o][lc].r+a[o][rc].l);
}
void update(int L,int R,int l,int r,int rt,int val,int o){
    if(L<=l&&r<=R){
        if(val) a[o][rt].l=a[o][rt].r=a[o][rt].len=r-l+1;
        else a[o][rt].l=a[o][rt].r=a[o][rt].len=0;
        a[o][rt].lz=val;
        return;
    }
    pd(l,r,rt,o);
    int m=l+r>>1;
    if(L<=m) update(L,R,l,m,lc,val,o);
    if(m<R) update(L,R,m+1,r,rc,val,o);
    pu(l,r,rt,o);
}
int query(int l,int r,int rt,int val,int o){
    if(a[o][rt].len<val) return -1;
    if(a[o][rt].l>=val) return l;
    pd(l,r,rt,o);
    int m=l+r>>1;
    if(a[o][lc].len>=val) return query(l,m,lc,val,o);
    if(a[o][lc].r+a[o][rc].l>=val) return m-a[o][lc].r+1;
    return query(m+1,r,rc,val,o);
}
int main()
{
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        printf("Case %d:\n",++cs);
        build(1,n,1);
        while(m--){
            scanf("%s",s);
            int x,y;
            if(s[0]=='D'){
                scanf("%d",&x);
                int le=query(1,n,1,x,0);
                if(le==-1) printf("fly with yourself\n");
                else{
                    printf("%d,let's fly\n",le);
                    update(le,le+x-1,1,n,1,0,0);
                }
            }
            else if(s[0]=='N'){
                scanf("%d",&x);
                int le=query(1,n,1,x,0);
                if(le==-1) le=query(1,n,1,x,1);
                if(le==-1) printf("wait for me\n");
                else{
                    printf("%d,don't put my gezi\n",le);
                    update(le,le+x-1,1,n,1,0,0);
                    update(le,le+x-1,1,n,1,0,1);
                }
            }
            else{
                scanf("%d %d",&x,&y);
                update(x,y,1,n,1,1,0);
                update(x,y,1,n,1,1,1);
                printf("I am the hope of chinese chengxuyuan!!\n");
            }
        }
    }
}