1. 程式人生 > 實用技巧 >線段樹 - dfs序對映至線段樹解決區間染色問題 / 時間戳 - HDU 3974 - Assign the task

線段樹 - dfs序對映至線段樹解決區間染色問題 / 時間戳 - HDU 3974 - Assign the task

線段樹 - dfs序對映至線段樹解決區間染色問題 / 時間戳 - HDU 3974 - Assign the task

解法1: 模擬多叉樹(資料極端的情況下,複雜度極大)

1. 分配任務操作

對於每一個分配任務操作,我們可以記錄分配的任務編號以及分配任務時間.

由於可能存在大量的任務分配操作,我們借鑑線段樹的思想,使用lazy陣列來延遲更新.此外,我們還需要記錄分配任務時間順序,這是因為,如果不記錄分配任務的時間順序,在後面更新lazy標記的時候,可能存在舊的任務覆蓋新的任務的情況.

int taskOf[N];
int isLazy[N];
void assign(int id,int task,int time){
    taskOf[id] = task;
    isLazy[id] = time;
}

2. 查詢操作

我們查詢某一個員工的任務,首先需要將它的所有祖先的lazy標記下放.我們應該從樹根下放lazy標記,然後一步步得到當前員工結點的任務.

這麼做就會遇到我們剛剛所講的問題:舊的任務覆蓋新的任務的情況. 試想這樣一種情況, A是B的上司,B是C的上司,我們先給A分配任務1,再給B分配任務2; 此時, lazy標記都沒有下放; 然後,我們查詢C當前的任務,那麼我們會先下放A的lazy標記給B,此時B的lazy標記被覆蓋了,然後B的lazy標記下放給C,得到C的任務是1.這顯然是錯誤的.因此,我們對於lazy下放的操作需加一個前置條件: 父結點的lazy時間戳要大於子結點的lazy時間戳才可下放

.

void query(int id){
    if(parentOf[id]){ 
        query(parentOf[id]);
    }

    if(Islazy[id]){
        for(vector<int> :: iterator it = childOf[id].begin(); it != childOf[id].end(); it++){
            if(Islazy[*it] < Islazy[id]){ 
// 如果根結點具備更新的時間戳,那麼子結點的時間戳也被更新; 如果子結點根本不存在時間戳(沒有更新點,是0),那麼也會直接更新
                taskOf[*it] = taskOf[id];
                Islazy[*it] = Islazy[id];
            }
        }
        Islazy[id] = 0;
    }
}

3.完整程式碼

#include <cstdio>
#include <vector>
#include <cstdlib>
using namespace std;
#define N 50000+50
vector<int> childOf[N];
int parentOf[N];
int taskOf[N];
int Islazy[N];

void assign(int id,int task,int time){
    taskOf[id] = task;    
    Islazy[id] = time;
}

void query(int id){
    if(parentOf[id]){ 
        query(parentOf[id]);
    }

    if(Islazy[id]){
        for(vector<int> :: iterator it = childOf[id].begin(); it != childOf[id].end(); it++){
            if(Islazy[*it] < Islazy[id]){ 
// 如果根結點具備更新的時間戳,那麼子結點的時間戳也被更新; 如果子結點根本不存在時間戳(沒有更新點,是0),那麼也會直接更新
                taskOf[*it] = taskOf[id];
                Islazy[*it] = Islazy[id];
            }
        }
        Islazy[id] = 0;
    }
}
    
int main(){
    int T; // 10
    scanf("%d",&T);
    for(int g = 1; g <= T; g++){
        printf("Case #%d:\n",g);
        int n,m; // 5e5
        int a,b;
        char cmd[5];
        scanf("%d",&n);
        for(int i = 1; i <= n; i++){
            childOf[i].clear();
            taskOf[i] = -1;
            Islazy[i] = 0;
            parentOf[i] = 0;
        }

        for(int i = 1; i < n; i++){
            scanf("%d%d",&a,&b);
            childOf[b].push_back(a);
            parentOf[a] = b;
        }

        scanf("%d",&m);
        for(int i = 1; i <= m; i++){
            scanf("%s",cmd);
            if(cmd[0] == 'T'){
                scanf("%d%d",&a,&b);
                // assign task b to employee a
                assign(a,b,i); // i 時間戳
            }else{
                scanf("%d",&a);
                // query task of employee a
                query(a);
                printf("%d\n",taskOf[a]);
            }
        }
    }
    system("pause");
    return 0;
}

然而,這種演算法當整棵樹為一條鏈狀時會達到最壞複雜度O(T*m*n),需要考慮更加穩定的解法*

解法2: dfs序對映至線段樹

我們可以記錄一棵樹的dfs序將其對映到區間上,例如一棵樹

我們通過dfs來訪問各個結點的順序是

如果再把重新回到該結點的狀態也標註出來,就是:

可以看出,通過標註dfs序,我們將樹的父子關係對映成了連續的區間關係,

即:

  • 1號結點影響的數字為$[1,2,4,7,8,5,3,6] \(, 即區間\)[1,8]$
  • 2號結點影響的數字為\([2,4,7,8,5]\),即區間\([2,6]\)
  • 3號結點影響的數字為\([3,6]\),即區間\([7,8]\)
  • 4號結點影響的數字為\([4,7,8]\),即區間\([3,5]\)
  • 5號結點影響的數字為\([5,5]\),即區間\([6,6]\)
  • 6號結點影響的數字為\([6,6]\),即區間\([7,7]\)
  • 7號結點影響的數字為\([7,7]\),即區間\([4,4]\)
  • 8號結點影響的數字為\([8,8]\),即區間\([5,5]\)

此時問題被轉化成了:對於區間[1,8],修改一段子區間的值,然後查詢某個孤立點的值

  • 對於一個修改操作:

題目給定一個員工和分配的任務,我們通過記錄dfs序來轉變成

修改區間[update_left,update_right]為value

  • 對於一個查詢操作:

題目給定一個員工的編號,我們查詢的是區間的某個孤立點的值

如查詢三號員工的任務,我們記錄的三號員工dfs序是第7位,那麼問題轉變為求區間[7,7]的值

剩下的操作就是線段樹的模板了,記得還是要加時間戳,因為它是一個染色問題,具有先後順序.

#include <cstdio>
#include <vector>
#include <cstdlib>
using namespace std;
#define N 50000+5

int mapPtr = 0;
int leftMap[N];
int rightMap[N];
int taskOf[N<<2];
int timeOf[N<<2];
int isRoot[N];
vector<int> childOf[N];

void Map(int root){
    leftMap[root] = ++mapPtr;
    for(vector<int> :: iterator it = childOf[root].begin(); it != childOf[root].end(); it++){
        Map(*it);
    }
    rightMap[root] = mapPtr;
}

void update(int left,int right,int root,int update_left,int update_right,int newTask,int time){ // [update_left,update_right] -> newTask
    if(update_left <= left && update_right >= right){
        if(time > timeOf[root]){
            taskOf[root] = newTask;
            timeOf[root] = time;
        }
    }else{
        int mid = (left+right)>>1;
        if(update_left <= mid){
            update(left,mid,root<<1,update_left,update_right,newTask,time);
        }
        if(update_right > mid){
            update(mid+1,right,root<<1|1,update_left,update_right,newTask,time);
        }
    }
}

int query(int left,int right,int root,int querypos){
    if(left == querypos && right == querypos){
        return taskOf[root];
    }else{
        int mid = (left+right)>>1;
        if(querypos <= mid){
            if(timeOf[root] > timeOf[root<<1]){
                taskOf[root<<1] = taskOf[root];
                timeOf[root<<1] = timeOf[root];
            }
            return query(left,mid,root<<1,querypos);
        }else{
            if(timeOf[root] > timeOf[root<<1|1]){
                taskOf[root<<1|1] = taskOf[root];
                timeOf[root<<1|1] = timeOf[root];
            }
            return query(mid+1,right,root<<1|1,querypos);
        }
    }
}


int main(){
    int T;
    scanf("%d",&T);
    
    for(int g = 1; g <= T; g++){
        printf("Case #%d:\n",g);
        int n;
        int a,b;
        scanf("%d",&n);
        for(int i = 1; i <= n; i++){
            childOf[i].clear();
            isRoot[i] = 1;
        }
        int temp = n<<2;
        for(int i = 1; i <= temp; i++){
            taskOf[i] = -1;
            timeOf[i] = 0;
        }
        mapPtr = 0;
        int rootId = 0;
        for(int i = 1; i < n; i++){
            scanf("%d%d",&a,&b);
            childOf[b].push_back(a);
            isRoot[a] = 0;
        }
        for(int i = 1; i <= n; i++){
            if(isRoot[i]){
                rootId = i;
                break;
            }
        }

        Map(rootId);

        int m;
        char cmd[5];
        scanf("%d",&m);
        for(int i = 1; i <= m; i++){
            scanf("%s",cmd);
            if(cmd[0] == 'T'){
                scanf("%d%d",&a,&b);
                // a->b
                update(1,mapPtr,1,leftMap[a],rightMap[a],b,i);
            }else{
                scanf("%d",&a);
                printf("%d\n",query(1,n,1,leftMap[a]));
            }
        }
    }
    system("pause");
    return 0;
}

簡單分析一下這個演算法的時間複雜度:

dfs序對映區間: O(n)
區間修改,單點查詢: O(logn)
總程式複雜度: O(T*(n+m*logn))