1. 程式人生 > 資訊 >“北京的士”上線: 100 多家巡遊計程車實現網約化運營

“北京的士”上線: 100 多家巡遊計程車實現網約化運營

前言

以下純屬口胡

正文

記得陣列開四倍。

普通線段樹

演算法思想為從樹根開始,往下遞迴。

定義為:

struct Node{
    int l,r/*左右兒子*/,val/*權值*/,tag/*懶惰標記*/;
}e[4000001];

建樹

很簡單,由根節點遞歸向下建樹,搜到葉子節點後,回溯更改區間值,程式碼為。

void build(int x,int l,int r) {
    e[x].l=l,e[x].r=r;
    if (l==r) {//搜到葉子節點
        e[x].val=val[l];
        return;
    }
    int mid=l+r>>1;
    build(x*2,l,mid);
    build(x*2+1,mid+1,r);
    //左右遞迴建樹
    e[x].val=e[x*2].val+e[x*2+1].val;//回溯賦值
}

修改

單點:很簡單,相信大家也都會(而且也基本用不到),就不提了。

區間:這裡就需要提到一個東西了,\(Lazy\;tag\)

這是個啥呢,舉個例子,你在區間修改時,如果從根節點開始修改,並且線段樹的深度還特別大,那麼就需要遞迴到葉子節點,複雜度可想而知。

\(Lazy\;tag\) 呢,就是在修改時,給當前節點打上標記,如果要查詢,就把標記下傳,達到優化的效果。

下傳函式的寫法為:

void pushdown(int x) {
    if (e[x].tag) {
        e[x*2].val+=e[x].tag*(e[x*2].r-e[x*2].l+1);
        e[x*2+1].val+=e[x].tag*(e[x*2+1].r-e[x*2+1].l+1);
        e[x*2].tag+=e[x].tag;
        e[x*2+1].tag+=e[x].tag;
        e[x].tag=0;
    }
}

得出了這個,修改函式的寫法也就十分簡單了,如下:

void updata(int x,int l,int r,int pos) {
    if (l<=e[x].l and r>=e[x].r) {
        e[x].val+=pos*(e[x].r-e[x].l+1);
        e[x].tag+=pos;
        return;
    }
    pushdown(x);
    int mid=e[x].l+e[x].r>>1;
    if (l<=mid) updata(x*2,l,r,pos);
    if (r>mid) updata(x*2+1,l,r,pos);
    e[x].val=e[x*2].val+e[x*2+1].val;
}

查詢

單點:依舊不提。

區間:和修改函式相似,依舊採用 \(Lazy\;tag\) 優化,程式碼如下:

int ask(int x,int l,int r) {
    if (l<=e[x].l and r>=e[x].r) return e[x].val;
    pushdown(x);
    int mid=e[x].l+e[x].r>>1;
    int ans=0;
    if (l<=mid) ans+=ask(x*2,l,r);
    if (r>mid) ans+=ask(x*2+1,l,r);
    return ans;
}

線段樹1 AC Code:

#include <iostream>

#define int long long

using namespace std;
int n,k,val[1000001];

struct Node{
    int l,r,val,tag;
}e[4000001];

void build(int x,int l,int r) {
    e[x].l=l,e[x].r=r;
    if (l==r) {
        e[x].val=val[l];
        return;
    }
    int mid=l+r>>1;
    build(x*2,l,mid);
    build(x*2+1,mid+1,r);
    e[x].val=e[x*2].val+e[x*2+1].val;
}

void pushdown(int x) {
    if (e[x].tag) {
        e[x*2].val+=e[x].tag*(e[x*2].r-e[x*2].l+1);
        e[x*2+1].val+=e[x].tag*(e[x*2+1].r-e[x*2+1].l+1);
        e[x*2].tag+=e[x].tag;
        e[x*2+1].tag+=e[x].tag;
        e[x].tag=0;
    }
}

void updata(int x,int l,int r,int pos) {
    if (l<=e[x].l and r>=e[x].r) {
        e[x].val+=pos*(e[x].r-e[x].l+1);
        e[x].tag+=pos;
        return;
    }
    pushdown(x);
    int mid=e[x].l+e[x].r>>1;
    if (l<=mid) updata(x*2,l,r,pos);
    if (r>mid) updata(x*2+1,l,r,pos);
    e[x].val=e[x*2].val+e[x*2+1].val;
}

int ask(int x,int l,int r) {
    if (l<=e[x].l and r>=e[x].r) return e[x].val;
    pushdown(x);
    int mid=e[x].l+e[x].r>>1;
    int ans=0;
    if (l<=mid) ans+=ask(x*2,l,r);
    if (r>mid) ans+=ask(x*2+1,l,r);
    return ans;
}

signed main() {
    cin>>n>>k;
    for (int i=1;i<=n;i++) cin>>val[i];
    build(1,1,n);
    for (int i=1,opt,x,y,tmp;i<=k;i++) {
        cin>>opt;
        if (opt==1) {
            cin>>x>>y>>tmp;
            updata(1,x,y,tmp);
        } else {
            cin>>x>>y;
            cout<<ask(1,x,y)<<endl;
        }
    }
}

zkw 線段樹

本文重點來了,先放一下線段樹1 AC 程式碼:

#include <iostream>
#include <cstdio>

#define MAXN 200005
#define int long long

using namespace std;
int n,N=1,m;
int tree[MAXN<<2],add[MAXN<<2];

void build() {
	for (;N<=n+1;N<<=1);
	for (int i=N+1;i<=N+n;i++) scanf("%d",tree+i);
	for (int i=N-1;i>=1;i--) tree[i]=tree[i<<1]+tree[i<<1|1];
}

void updata(int l,int r,int k) {
	int lnum=0,rnum=0,nnum=1;
	for (l=N+l-1,r=N+r+1;l^r^1;l>>=1,r>>=1,nnum<<=1) {
		tree[l]+=k*lnum,tree[r]+=k*rnum;
		if (~l&1) add[l^1]+=k,tree[l^1]+=k*nnum,lnum+=nnum;
		if (r&1) add[r^1]+=k,tree[r^1]+=k*nnum,rnum+=nnum;
	}
	for (;l;l>>=1,r>>=1) tree[l]+=k*lnum,tree[r]+=k*rnum;
}

int ask(int l,int r) {
	int lnum=0,rnum=0,nnum=1;
	int ans=0;
	for (l=N+l-1,r=N+r+1;l^r^1;l>>=1,r>>=1,nnum<<=1) {
		if (add[l]) ans+=add[l]*lnum;
		if (add[r]) ans+=add[r]*rnum;
		if (~l&1) ans+=tree[l^1],lnum+=nnum;
		if (r&1) ans+=tree[r^1],rnum+=nnum;
	}
	for (;l;l>>=1,r>>=1) ans+=add[l]*lnum+add[r]*rnum;
	return ans;
}

void work() {
	cin>>n>>m;
	build();
	for (int i=1,opt,x,y,k;i<=m;i++) {
		cin>>opt>>x>>y;
		if (opt==1) {
			cin>>k;
			updata(x,y,k);
		} else if (opt==2) {
			cout<<ask(x,y)<<endl;
		}
	}
}

signed main() {
	work();
}

是不是發現短了很多。

其實,不止是長度變短了,並且速度也快了不少,和普通線段樹對比如下。

普通線段樹:

zkw 線段樹:

為什麼會這樣呢?

因為 zkw 線段樹和普通線段樹的演算法不一樣(廢話),普通線段樹是從根節點到葉子結點,而 zkw 線段樹是從葉子節點到根節點。

???

從葉子節點到根節點?那不是樹狀陣列嗎?

啊,其實不然,zkw 線段樹相較於樹狀陣列的的操作可以更多一點(所以複雜度也相應變高了)。

建樹

從葉子結點往上建樹,咋建呢?

很簡單,我們需要一個變數 \(N\),這個變數是記錄葉子結點的第一位的編號減一的位置,而要求得它我們就需要一個神奇的 for 迴圈,如下:

for (;N<=n+1;N<<=1);

這個 for 的意思是隻要 \(N\) 的編號不到葉子結點(因為葉子結點為 \(n\) 的滿二叉樹,其節點數為 \(2\times n-1\)),就倍增下去。

接著到了輸入資料(沒錯 zkw 建樹需要輸入資料),直接把資料存到葉子結點即可,接著往上推,直到推到根節點,程式碼如下:

void build() {
	for (;N<=n+1;N<<=1);
	for (int i=N+1;i<=N+n;i++) scanf("%lld",tree+i);
	for (int i=N-1;i>=1;i--) tree[i]=tree[i<<1]+tree[i<<1|1];
}

修改

單點:不提。

區間:這裡需要連個指標:\(l\)\(r\) ,他們代表的是左區間減一和右區間加一,從下往上推,直到推到父節點相等停止。

是不是覺得少了什麼,沒錯 \(Lazy\;tag\),其實 zkw 線段樹的 \(Lazy\;tag\) 和不同線段樹還不一樣,他才用了標記永久化的思想,就是讓它一直懶下去,然後如果你位運算好一點的話,就可以寫出程式碼了:

void updata(int l,int r,int k) {
	int lnum=0/*l 遍歷到的節點個數*/,rnum=0/*r 遍歷到的節點個數*/,nnum=1/*區間節點個數*/;
	for (l=N+l-1,r=N+r+1;l^r^1;l>>=1,r>>=1,nnum<<=1) {
		tree[l]+=k*lnum,tree[r]+=k*rnum;
		if (~l&1) add[l^1]+=k,tree[l^1]+=k*nnum,lnum+=nnum;
		if (r&1) add[r^1]+=k,tree[r^1]+=k*nnum,rnum+=nnum;
	}
	for (;l;l>>=1,r>>=1) tree[l]+=k*lnum,tree[r]+=k*rnum;
}

看到這一大坨位運算是不是很蒙,別急我來解釋一下。

l^r^1:什麼意思呢很簡單就是判斷 \(l\)\(r\) 的父節點是否相同,是返回 \(0\),否返回 \(1\)

~l&1r&1:判斷 \(l\)\(r\) 是否為根節點。

最後一句:其實就是如果 \(l\)\(r\) 都不為 \(1\) 時,對根節點的更改操作。

查詢

單點:不提。

區間:和上面類似,也需要 \(l\)\(r\)\(Lazy\;tag\),大致操作類似,只不過需要多一句:

if (add[l]) ans+=add[l]*lnum;
if (add[r]) ans+=add[r]*rnum;

(好吧是兩句)

這兩句也就是判斷是否有懶惰標記,整體程式碼如下:

int ask(int l,int r) {
	int lnum=0,rnum=0,nnum=1;
	int ans=0;
	for (l=N+l-1,r=N+r+1;l^r^1;l>>=1,r>>=1,nnum<<=1) {
		if (add[l]) ans+=add[l]*lnum;
		if (add[r]) ans+=add[r]*rnum;
		if (~l&1) ans+=tree[l^1],lnum+=nnum;
		if (r&1) ans+=tree[r^1],rnum+=nnum;
	}
	for (;l;l>>=1,r>>=1) ans+=add[l]*lnum+add[r]*rnum;
	return ans;
}

\(finally\)

zkw 線段樹還是很有用的(畢竟線段樹那常數大的呀),但是弊端很明顯,不能處理優先順序問題,如 p3373

就這樣,byebye。