HDU3974(時間戳建成多叉樹)(DFS順序建立線段樹)
阿新 • • 發佈:2018-12-13
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<vector> using namespace std; const int maxn=50000+5; vector<int>pre[maxn]; //為每一個頂點分配一個動態陣列儲存它的下屬 bool vis[maxn];//表示有沒有上司,有賦值為1 int ltree[maxn],rtree[maxn]; //為每個頂點分配左端點和右端點,中間值表示下屬 int n,m,L,R; int v,u; int id; int tree[maxn<<4]; void build_tree() { memset(tree,-1,sizeof(tree)); memset(vis,0,sizeof(vis)); memset(ltree,-1,sizeof(ltree)); memset(rtree,-1,sizeof(rtree)); id=1; } void dfs(int s,int &id) { ltree[s]=id++; for(int i=0; i<pre[s].size(); ++i) dfs(pre[s][i],id); rtree[s]=id++; } void pushdown(int rt) { if(tree[rt]!=-1) { tree[rt<<1|1]=tree[rt<<1]=tree[rt]; tree[rt]=-1; } } void update(int p,int l,int r,int rt) { if(L<=l&&r<=R) { tree[rt]=p; return; } pushdown(rt); int m=(l+r)>>1; if(L<=m) update(p,l,m,rt<<1); if(m<R) update(p,m+1,r,rt<<1|1); } int query(int l,int r,int rt) { if(l==r) return tree[rt]; pushdown(rt); int m=(l+r)>>1; if(L<=m) query(l,m,rt<<1); else query(m+1,r,rt<<1|1); } int main() { int t,T; char str[2]; scanf("%d",&t); T=t; while(t--) { build_tree(); scanf("%d",&n); for(int i=1; i<=n; ++i) pre[i].clear(); for(int i=0; i<n-1; ++i) { scanf("%d%d",&u,&v); pre[v].push_back(u); vis[u]=1; } for(int i=1; i<=n; ++i) if(!vis[i]) dfs(i,id); scanf("%d",&m); printf("Case #%d:\n",T-t); for(int i=0;i<m;++i) { scanf("%s",&str); if(str[0]=='C') { scanf("%d",&u); L=ltree[u]; printf("%d\n",query(1,n<<1,1)); } else { scanf("%d%d",&u,&v); L=ltree[u]; R=rtree[u]; update(v,1,n<<1,1); } } } return 0; }
最開始沒有思路,看到大佬的部落格知道了怎麼建樹。
1.注意題目中的所有員工標號的範圍(1-n)
3.下面解釋程式原理,方便理清一下思路:
【1】對於n個員工(編號從1-n)每個員工i線上段樹中對應一個區間ltree[i]~rtree[i]表示員工i的左右端點。實現了將員工i的資訊對映到線段樹中。
【2】在dfs建樹的過程中不難發現:左右端點中間的端點值表徵了員工i的下屬。
員工i的左右端點如果在另一個員工j的左右端點中間表示i的上司是j。
【3】查詢函式一定精確查詢到葉節點。
【4】主函式從整個公司的boss開始dfs建樹。按照dfs順序為每個員工線上段樹中的位置【左右端點】分配了兩個id。據此可以解釋為什麼線段樹的元素個數是n<<1(2*n)個,因為員工數為n個,每個員工線上段樹中對應兩個id,解釋完畢。
這樣可以清楚的理解pushdown函式中tree[rt]最後賦值為-1的原因。
再次重新說明一下:tree[rt]!=-1是更新函式造成的,其作用兩個:記錄標記位置【標記區間】和標記值。這兩個作用的最主要目的是利用線段樹的結構不進行更新到底層的操作,大大降低時間複雜度。
這樣在遍歷到當前被標記的位置或區間時,如果下推後不進行初始化(刪除操作),那麼它會繼續發揮這兩個作用而造成錯誤(因為標記位置因為下推而發生改變,標記值也已經被兩個子區間繼承,因而這個位置不在具有表示標記位置的作用,所以會發生錯誤)。