線段樹合集
POJ - 2528 - Mayor's posters
題目連結<http://poj.org/problem?id=2528>
題意:
在範圍為1e9的瓷磚上,依次覆蓋1e4張海報,每張海報各不相同,問最後能看到的海報有多少張。
題解:
離散加區間修改和最後一次的區間查詢。
離散有兩種考慮方式:
- 離散邊界,也就是說對於瓷磚區間r+1或者l-1。
- 離散區間,這樣容易忽略離散後的順序相鄰但實際的位置並不相鄰的情況,可以考慮在每個位置不相鄰的節點中間另外加一個節點即可。
#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>
題意:
區間修改:區間內的每個數變成原來數字的平方根。
區間查詢:查詢區間和。
數字的範圍小於。
題解:
對於範圍在內的,最多做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>
題意:
對於一串數字,初始化為零,一共有四種操作:
- 對一段區間加上一個值。
- 對一段區間乘上一個值。
- 把一段區間內每個數字變成同一個值。
- 對一段區間查詢
題解:
首先考慮查詢,因為的值只有三種,可以對每種都進行維護。
乘上一個值以及變成一個值都很好維護,加法可以拆開來推:
- 平方和:
- 立方和:
還有一個問題,這三種操作之間會互相影響,不能簡單的分開維護。可以這麼考慮:,也就是說每乘上一個數,區間上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>
題意:
一共有兩個操作:
- 從第A個位置開始放F朵花,有空位就放。直到放完或者沒地方放為止。如果一朵花都沒有放就輸出“Can not put any one.”,否則輸出第一朵放花的位置和最後一朵放花的位置。
- 把一段區間的花朵清空。
題解:
如果能夠放完,那麼就二分可以放花的區間。利用線段樹查詢確定第一處放花和最後一處放花的位置。
#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");
}
}
}
}