線段樹 - 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))