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;  
}  

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值