线段树 - 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序将其映射到区间上,例如一棵树

image-20201101160520288

我们通过dfs来访问各个结点的顺序是

image-20201101161103125

如果再把重新回到该结点的状态也标注出来,就是:

image-20201101161413424

可以看出,通过标注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))
posted @ 2020-11-01 16:30  popozyl  阅读(90)  评论(0编辑  收藏  举报