dfs序基本類型總結

大致看了下dfs序的題型,大致清楚了大致的解題思路。。。但是對於一些題目還是比較無力。。。。

dfs序比較重要的性質:一棵子樹的所有節點在dfs序裏是連續一段,主要就是利用這個性質來解題


題型一:對某個點X權值加上一個數W,查詢某個子樹X裏所有點權值和。

解:列出dfs序,實現修改一個數,查詢一段序列的和,顯然這個序列可以用樹狀數組維護。

/*
poj3321
樹狀數組直接在第一次出現的位置+1,-1好了,對其他兄弟樹沒有影響,因爲兄弟樹是求區間,前面的+1,-1已經抵消掉了
*/

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <string>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int maxn = 100005;

struct ppp{
	int v,nex;
}e[maxn * 4] ;
int head[maxn],tole,n;
void make_edge(int u,int v){
	e[tole].v = v;e[tole].nex = head[u];head[u] = tole++;
}
int qian[maxn],hou[maxn],vis[maxn];
int sum[maxn * 2];
int cnt_node;

void init(){
	for(int i = 0;i < 2 * maxn;i++){
		head[i >> 1] = -1;sum[i] = 0;
		vis[i >> 1] = 1;
	}
	tole = 0;
	cnt_node = 0;
}


void dfs(int u,int pre){
	qian[u] = ++cnt_node;
	for(int i = head[u];~i;i = e[i].nex){
		int v = e[i].v;
		if(v == pre)continue;
		dfs(v,u);

	}
	hou[u] = ++cnt_node;
}
int lowbit(int x){
	return x & -x;
}
int query(int x){
	int ret = 0;
	while(x >= 1){
		ret += sum[x];
		x -= lowbit(x);
	}	
	return ret;
}
void add(int x,int v){
	while(x <= cnt_node){
		sum[x] += v;
		x += lowbit(x);
	}
}
int main(){
	while(~scanf("%d",&n)){
		init();
		for(int i = 1,a,b;i <= n - 1;i++){
			scanf("%d%d",&a,&b);
			make_edge(a,b);
			make_edge(b,a);
		}
		dfs(1,-1);
		for(int i = 1;i <= n;i++){
			add(qian[i],1);
		}
		int q;
		scanf("%d",&q);
		char c;int a;
		while(q--){
			scanf(" %c%d",&c,&a);
			if(c == 'Q'){
				int now = query(hou[a]) - query(qian[a] - 1);
				printf("%d\n",now);
			}else {
				if(vis[a] > 0){
					add(qian[a],-1);
				}else {
					add(qian[a],1);
				}
				vis[a] = !vis[a];
			}
		}
	}
	
}



題型二:對X到Y的最短路上所有點權值加上一個數W,查詢某個點的權值。

解:並沒有找到該類型的題目。。。。假設有這種題目吧。。若X到Y上的點的權值都加上W,那麼其實就是X到根的權值加上W,Y到根的點權值加上W,lca(X,Y)到根的權值減去W,parent(lca(X,Y))到根的點權值減去W。

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define FOR(i,a,b) for(int i = a;i <= b;i++)
using namespace std;
typedef long long ll;
const int maxn = 100005;
const int INF = 0x3f3f3f3f;
struct ppp
{	
	int v,nex;
}e[maxn * 2];
int head[maxn],q,n;
int dp[maxn * 2][19],tole;
int dep[maxn],pos[maxn],last[maxn * 2];
int cnt;
int st[maxn],ed[maxn];
void make_edge(int u,int v)
{
	e[tole].v = v,e[tole].nex = head[u];head[u] = tole++;
}
int sum[maxn * 2],parent[maxn];
inline int lowbit (int x){
	return x & -x;
}
void add(int x,int v){
	while(x <= cnt){
		sum[x] += v;
		x += lowbit(x);
	}
}
int que(int x){
	int ret = 0;
	while(x >= 1){
		ret += sum[x];
		x -= lowbit(x);
	}
	return ret;
}
void dfs(int u,int fa,int deep)
{
	parent[u] = fa;
	dep[u] = deep;
	pos[u] = ++cnt;
	st[u] = cnt;
	last[cnt] = u;
	int v;
	for(int i = head[u];~i;i = e[i].nex)
	{
		v = e[i].v;
		if(v == fa)continue;
		dfs(v,u,deep + 1);
		pos[u] = ++cnt;
		last[cnt] = u;
	}
	ed[u] = cnt;
}
void ST()
{
	for(int i = 1;i <= cnt;i++)
		dp[i][0] = last[i];
	for(int j = 1;(1 << j) <= cnt;j++)
		for(int i = 1;i + (1 << j) - 1 <= cnt;i++)
		{
			dp[i][j] = dp[i][j - 1];
			if(dep[dp[i + (1 << (j - 1))][j - 1]] < dep[dp[i][j - 1]])dp[i][j] = dp[i + (1 << (j - 1))][j - 1];
		}
}
int query(int l,int r)
{
	int k = 0;
	while((1 << (k + 1)) <= r - l + 1)k++;
	if(dep[dp[l][k]] < dep[dp[r - (1 << k) + 1][k]])
		return dp[l][k];
	else return dp[r - (1 << k) + 1][k];
}
void init()
{
	mem(head,-1);
	tole = 0;
	cnt = 0;
	mem(sum,0);
}
int main()
{
	while(~scanf("%d",&n))
	{
		init();
		for(int i = 1,a,b;i <= n - 1;i++){
			scanf("%d%d",&a,&b);
			make_edge(a,b);
			make_edge(b,a);
		}
		dfs(1,-1,1);
		ST();
		scanf("%d",&q);//查詢個數
		char c;
		int a,b;
		while(q--){
			scanf(" %c",&c);
			if(c == 'Q'){
				scanf("%d",&a);
				printf("%d\n",que(ed[a]) - que(st[a] - 1));
			}else {
				int v;
				scanf("%d%d%d",&a,&b,&v);
				int aa = pos[a],bb = pos[b];
				if(aa > bb)swap(aa,bb);
				int lca = query(aa,bb);
				add(st[a],v);//這裏在樹狀數組上加加減減感覺挺難理解的,可以寫出dfs序畫一畫
				add(st[b],v);
				add(st[lca],-v);
				if(lca != 1)
				add(st[parent[lca]],-v);
			}
		}
	}
}


題型三:

從X到Y的點加上或者減去一個W,求某個點子樹內的所有點的權值和

假設X在Y的子樹內,那麼對於詢問點Y,詢問值會加上W * (depth(X) - depth(Y) + 1)。

拆開後爲W * (depth(x) + 1) - W * (depth(Y)) 。因爲對於詢問的是Y,由此可見此式子跟X無關,所以可以開設兩個樹狀數組,一個保存W * (depth(x) + 1),另一個保存W * depth(Y),對於每一個部分維護,維護的方式類似題型二。

以下代碼非本人寫,供參考

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int MAXN = 1e5+10;

vector<int> edge[MAXN];
int s[2*MAXN];
int s1[2*MAXN];
int seq[2*MAXN];
int seq1[2*MAXN];
int depth[2*MAXN];
int first[MAXN];
int dp[2*MAXN][25];
int st[MAXN];
int ed[MAXN];
int parent[MAXN];
int cnt, num;

int Lowbit(int x)
{
    return x & (-x);
}

void Add(int x, int val, int n)
{
    if(x <= 0) return;
    for(int i = x; i <= n; i += Lowbit(i)) {
        s[i] += val;
    }
}

void Add1(int x, int val, int n)
{
    if(x <= 0) return;
    for(int i = x; i <= n; i += Lowbit(i)) {
        s1[i] += val;
    }
}

int Sum(int x)
{
    int res = 0;
    for(int i = x; i > 0; i -= Lowbit(i)) {
        res += s[i];
    }
    return res;
}

int Sum1(int x)
{
    int res = 0;
    for(int i = x; i > 0; i -= Lowbit(i)) {
        res += s1[i];
    }
    return res;
}

void Dfs(int u, int fa, int dep)
{
    parent[u] = fa;
    seq[++cnt] = u;
    seq1[++num] = u;
    first[u] = num;
    depth[num] = dep;
    st[u] = cnt;
    int len = edge[u].size();
    for(int i = 0; i < len; i++) {
        int v = edge[u][i];
        if(v != fa) {
            Dfs(v, u, dep+1);
            seq1[++num] = u;
            depth[num] = dep;
        }
    }
    seq[++cnt] = u;
    ed[u] = cnt;
}

void RMQ_Init(int n)
{
    for(int i = 1; i <= n; i++) {
        dp[i][0] = i;
    }
    for(int j = 1; (1 << j) <= n; j++) {
        for(int i = 1; i + (1 << j) - 1 <= n; i++) {
            int a = dp[i][j-1], b = dp[i + (1 << (j-1))][j-1];
            dp[i][j] = depth[a] < depth[b] ? a : b;
        }
    }
}

int RMQ_Query(int l, int r)
{
    int k = 0;
    while((1 << (k + 1)) <= r - l + 1) k++;
    int a = dp[l][k], b = dp[r-(1<<k)+1][k];
    return depth[a] < depth[b] ? a : b;
}

int LCA(int u, int v)
{
    int a = first[u], b = first[v];
    if(a > b) a ^= b, b ^= a, a ^= b;
    int res = RMQ_Query(a, b);
    return seq1[res];
}

void Init(int n)
{
    for(int i = 0; i <= n; i++) {
        edge[i].clear();
    }
    memset(s, 0, sizeof(s));
}

int main()
{
    int n, op;
    int u, v, w;
    int cmd;

    while(scanf("%d %d", &n, &op) != EOF) {
        Init(n);
        for(int i = 0; i < n-1; i++) {
            scanf("%d %d", &u, &v);
            edge[u].push_back(v);
            edge[v].push_back(u);
        }
        cnt = 0, num = 0;
        Dfs(1, -1, 0);
        RMQ_Init(num);
        while(op--) {
            scanf("%d", &cmd);
            if(cmd == 0) {
                scanf("%d %d %d", &u, &v, &w);
                int lca = LCA(u, v);
				//以下維護跟求值其實跟題型二是一樣的,就是分兩個樹狀數組 
                Add(st[u], w * depth[first[u]] + w, cnt);
                Add1(st[u], w, cnt);
                Add(st[v], w * depth[first[v]] + w, cnt);
                Add1(st[v], w, cnt);
                Add(lca, -(w * depth[first[lca]] + w), cnt);
                Add1(lca, -w, cnt);
                Add(parent[lca], -(w * depth[first[parent[lca]]] + w), cnt);
                Add1(parent[lca], -w, cnt);
            }
            else if(cmd == 1) {
                scanf("%d", &u);
                printf("%d\n", Sum(ed[u]) - Sum(st[u] - 1) - depth[first[u]] * (Sum1(ed[u]) - Sum1(st[u] - 1)));
            }
        }
    }

    return 0;
}


題型四:對於某個節點X加上一個W,查詢X到Y路徑上的所有點的權值和。ps:這個感覺比前面的都容易想到啊。。。。

那麼加上一個W的維護就跟題型一的維護差不多。

查詢的值就等於:   X到根的權值和 + Y到跟的權值和 - LCA(X,Y)到根的權值和 - parent(LCA(X,Y))到根的權值和。

思路很簡單,貼一下別人的代碼:

const int MAXN = 1e5+10;

vector<int> edge[MAXN];
int s[2*MAXN];
int s1[2*MAXN];
int seq[2*MAXN];
int seq1[2*MAXN];
int depth[2*MAXN];
int first[MAXN];
int dp[2*MAXN][25];
int st[MAXN];
int ed[MAXN];
int parent[MAXN];
int cnt, num;

int Lowbit(int x)
{
    return x & (-x);
}

void Add(int x, int val, int n)
{
    if(x <= 0) return;
    for(int i = x; i <= n; i += Lowbit(i)) {
        s[i] += val;
    }
}

int Sum(int x)
{
    int res = 0;
    for(int i = x; i > 0; i -= Lowbit(i)) {
        res += s[i];
    }
    return res;
}

void Dfs(int u, int fa, int dep)
{
    parent[u] = fa;
    seq[++cnt] = u;
    seq1[++num] = u;
    first[u] = num;
    depth[num] = dep;
    st[u] = cnt;
    int len = edge[u].size();
    for(int i = 0; i < len; i++) {
        int v = edge[u][i];
        if(v != fa) {
            Dfs(v, u, dep+1);
            seq1[++num] = u;
            depth[num] = dep;
        }
    }
    seq[++cnt] = u;
    ed[u] = cnt;
}

void RMQ_Init(int n)
{
    for(int i = 1; i <= n; i++) {
        dp[i][0] = i;
    }
    for(int j = 1; (1 << j) <= n; j++) {
        for(int i = 1; i + (1 << j) - 1 <= n; i++) {
            int a = dp[i][j-1], b = dp[i + (1 << (j-1))][j-1];
            dp[i][j] = depth[a] < depth[b] ? a : b;
        }
    }
}

int RMQ_Query(int l, int r)
{
    int k = 0;
    while((1 << (k + 1)) <= r - l + 1) k++;
    int a = dp[l][k], b = dp[r-(1<<k)+1][k];
    return depth[a] < depth[b] ? a : b;
}

int LCA(int u, int v)
{
    int a = first[u], b = first[v];
    if(a > b) a ^= b, b ^= a, a ^= b;
    int res = RMQ_Query(a, b);
    return seq1[res];
}

void Init(int n)
{
    for(int i = 0; i <= n; i++) {
        edge[i].clear();
    }
    memset(s, 0, sizeof(s));
}

int main()
{
    int n, op;
    int u, v, w;
    int cmd;

    while(scanf("%d %d", &n, &op) != EOF) {
        Init(n);
        for(int i = 0; i < n-1; i++) {
            scanf("%d %d", &u, &v);
            edge[u].push_back(v);
            edge[v].push_back(u);
        }
        cnt = 0, num = 0;
        Dfs(1, -1, 0);
        RMQ_Init(num);
        while(op--) {
            scanf("%d", &cmd);
            if(cmd == 0) {
                scanf("%d %d", &u, &w);
                Add(st[u], w, cnt);
                Add(ed[u], -w, cnt);
            }
            else if(cmd == 1) {
                scanf("%d %d", &u, &v);
                int lca = LCA(u, v);
                printf("%d\n", Sum(st[u]) + Sum(st[v]) - Sum(st[lca]) - Sum(st[parent[lca]]));
            }
        }
    }

    return 0;
}


題型五:對於一個點X的子樹內的所有點加上一個值W,查詢某個點的值。

那麼只要在子樹總都+w就好了,就是維護區間就是st[X] + w,ed[X] - w。

查詢只要找que(X)就好了,注意如果點有原值的話要加上原值。

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define FOR(i,a,b) for(int i = a;i <= b;i++)
using namespace std;
typedef long long ll;
const int maxn = 100005;
const int INF = 0x3f3f3f3f;
struct ppp
{	
	int v,nex;
}e[maxn * 2];
int head[maxn],q,n;
int dp[maxn * 2][19],tole;
int dep[maxn],pos[maxn],last[maxn * 2];
int cnt;
int st[maxn],ed[maxn];
void make_edge(int u,int v)
{
	e[tole].v = v,e[tole].nex = head[u];head[u] = tole++;
}
int sum[maxn * 2],parent[maxn];
inline int lowbit (int x){
	return x & -x;
}
void add(int x,int v){
	while(x <= cnt){
		sum[x] += v;
		x += lowbit(x);
	}
}
int que(int x){
	int ret = 0;
	while(x >= 1){
		ret += sum[x];
		x -= lowbit(x);
	}
	return ret;
}
void dfs(int u,int fa,int deep)
{
	parent[u] = fa;
	dep[u] = deep;
	pos[u] = ++cnt;
	st[u] = cnt;
	last[cnt] = u;
	int v;
	for(int i = head[u];~i;i = e[i].nex)
	{
		v = e[i].v;
		if(v == fa)continue;
		dfs(v,u,deep + 1);
		pos[u] = ++cnt;
		last[cnt] = u;
	}
	ed[u] = cnt;
}
void ST()
{
	for(int i = 1;i <= cnt;i++)
		dp[i][0] = last[i];
	for(int j = 1;(1 << j) <= cnt;j++)
		for(int i = 1;i + (1 << j) - 1 <= cnt;i++)
		{
			dp[i][j] = dp[i][j - 1];
			if(dep[dp[i + (1 << (j - 1))][j - 1]] < dep[dp[i][j - 1]])dp[i][j] = dp[i + (1 << (j - 1))][j - 1];
		}
}
int query(int l,int r)
{
	int k = 0;
	while((1 << (k + 1)) <= r - l + 1)k++;
	if(dep[dp[l][k]] < dep[dp[r - (1 << k) + 1][k]])
		return dp[l][k];
	else return dp[r - (1 << k) + 1][k];
}
void init()
{
	mem(head,-1);
	tole = 0;
	cnt = 0;
	mem(sum,0);
}
int main()
{
	while(~scanf("%d",&n))
	{
		init();
		for(int i = 1,a,b;i <= n - 1;i++){
			scanf("%d%d",&a,&b);
			make_edge(a,b);
			make_edge(b,a);
		}
		dfs(1,-1,1);
		ST();
		scanf("%d",&q);//查詢個數
		char c;
		int a,b;
		while(q--){
			scanf(" %c",&c);
			if(c == 'Q'){
				scanf("%d",&a);
				printf("%d\n",que(st[a]));//其中這個是更改後的值統計,如果有原值的話要加上原值
			}else {
				int w;
				scanf("%d%d",&a,&w);
				add(st[a],w);
				add(ed[a],-w);
			}
		}
	}
}



題型六:對子樹X裏所有的節點加上一個值W,查詢某個子樹的權值和。

明顯的區間修改,區間查詢,那麼。。。線段樹。。

修改:就在st[X]到ed[X]上都加一個W

查詢:直接查詢st[X]到ed[X],記住都到的值除以2,因爲每個點都算了兩遍

代碼就不掛了。



題型七:對於子樹X內的所有點都加上一個值W,查詢X到Y之間的路徑上所有點權和。

那麼這題就跟題型四很像,就是修改的時候改成修改一段區間,從X到Y路徑上的話直接就套用題型四的做法。

貼一下別人的代碼:

typedef struct {
    int l, r, sum, add;
} Seg;

const int MAXN = 1e5+10;

Seg T[4*MAXN];
vector<int> edge[MAXN];
int s[2*MAXN];
int s1[2*MAXN];
int seq[2*MAXN];
int seq1[2*MAXN];
int depth[2*MAXN];
int first[MAXN];
int dp[2*MAXN][25];
int parent[MAXN];
int st[MAXN];
int ed[MAXN];
int cnt, cnt1;

int Lowbit(int x)
{
    return x & (-x);
}

void Add(int x, int val, int n)
{
    if(x <= 0) return ;
    for(int i = x; i <= n; i += Lowbit(i)) {
        s[i] += val;
    }
}

void Add1(int x, int val, int n)
{
    if(x <= 0) return ;
    for(int i = x; i <= n; i += Lowbit(i)) {
        s1[i] += val;
    }
}

int Sum(int x)
{
    int res = 0;
    for(int i = x; i > 0; i -= Lowbit(i)) {
        res += s[i];
    }
    return res;
}

int Sum1(int x)
{
    int res = 0;
    for(int i = x; i > 0; i -= Lowbit(i)) {
        res += s1[i];
    }
    return res;
}

void RMQ_Init(int n)
{
    for(int i = 1; i <= n; i++) {
        dp[i][0] = i;
    }
    for(int j = 1; (1 << j) <= n; j++) {
        for(int i = 1; i + (1 << j) - 1 <= n; i++) {
            int a = dp[i][j-1], b = dp[i + (1 << (j-1))][j-1];
            dp[i][j] = depth[a] < depth[b] ? a : b;
        }
    }
}

int RMQ_Query(int l, int r)
{
    int k = 0;
    while((1 << (k + 1)) <= r - l + 1) k++;
    int a = dp[l][k], b = dp[r-(1 << k)+1][k];
    return depth[a] < depth[b] ? a : b;
}

int LCA(int u, int v)
{
    int a = first[u], b = first[v];
    if(a > b) a ^= b, b ^= a, a ^= b;
    int res = RMQ_Query(a, b);
    return seq1[res];
}

void Dfs(int u, int fa, int dep)
{
    seq[++cnt] = u;
    seq1[++cnt1] = u;
    first[u] = cnt1;
    parent[u] = fa;
    depth[cnt1] = dep;
    st[u] = cnt;
    int len = edge[u].size();
    for(int i = 0; i < len; i++) {
        int v = edge[u][i];
        if(v != fa) {
            Dfs(v, u, dep+1);
            seq1[++cnt1] = u;
            depth[cnt1] = dep;
        }
    }
    seq[++cnt] = u;
    ed[u] = cnt;
}

void Init(int n)
{
    for(int i = 0; i <= n; i++) {
        edge[i].clear();
    }
    memset(s, 0, sizeof(s));
    memset(s1, 0, sizeof(s1));
}

void Debug()
{
    int u, v;
    while(1) {
        scanf("%d %d", &u, &v);
        printf("The LCA of %d %d is %d\n", u, v, LCA(u, v));
    }
}

int main()
{
    int n, op;
    int u, v, w;
    int cmd;

    while(scanf("%d %d", &n, &op) != EOF) {
        Init(n);
        for(int i = 0; i < n-1; i++) {
            scanf("%d %d", &u, &v);
            edge[u].push_back(v);
            edge[v].push_back(u);
        }
        cnt = cnt1 = 0;
        Dfs(1, 0, 0);
        RMQ_Init(cnt1);
        while(op--) {
            scanf("%d", &cmd);
            if(cmd == 0) {
                scanf("%d %d", &u, &w);
                Add(st[u], w * (1 - depth[first[u]]), cnt);
                Add(ed[u], -w * (1 - depth[first[u]]), cnt);
                Add1(st[u], w, cnt);
                Add1(ed[u], -w, cnt);
            }
            else if(cmd == 1) {
                scanf("%d %d", &u, &v);
                int lca = LCA(u, v);
                int par = parent[lca];
                int ans = Sum(st[u]);
                ans += depth[first[u]] * Sum1(st[u]);
                ans += Sum(st[v]);
                ans += depth[first[v]] * Sum1(st[v]);
                ans -= Sum(st[lca]);
                ans -= depth[first[lca]] * Sum1(st[lca]);
                ans -= Sum(st[par]);
                ans -= depth[first[par]] * Sum1(st[par]);
                printf("%d\n", ans);
            }
        }
    }

    return 0;
}



















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章