題解:BZOJ4399(魔法少女LJJ)
題面:
在森林中見過會動的樹,在沙漠中見過會動的仙人掌過後,魔法少女LJJ已經覺得自己見過世界上的所有稀奇古怪的事情了 LJJ感嘆道“這裡真是個迷人的綠色世界,空氣清新、淡雅,到處散發著醉人的奶漿味;小猴在枝頭悠來蕩去,好不自在;各式各樣的鮮花爭相開放,各種 樹枝的枝頭掛滿沉甸甸的野果;鳥兒的歌聲婉轉動聽,小河裡飄著落下的花瓣真是人間仙境” SHY覺得LJJ還是太naive,一天,SHY帶著自己心愛的圖找到LJJ,對LJJ說:“既然你已經見識過動態樹,動態仙人掌了,那麼今天就來見識一下動態圖吧” LJJ:“要支援什麼操 作?” SHY:“ 1.新建一個節點,權值為x。 2.連線兩個節點。 3.將一個節點a所屬於的聯通快內權值小於x的所有節點權值變成x。 4.將一個節點a所屬於的聯通快內權值大於x的所有節點權值變成x。 5.詢問一個節點a所屬於的聯通塊內的第k小的權值是多少。 6.詢問一個節 點a所屬聯通快內所有節點權值之積與另一個節點b所屬聯通快內所有節點權值之積的大小。 7.詢問a所在聯通快內節點的數量 8.若兩個節點a,b直接相連,將這條邊斷開。 9.若節點a存在,將這個點刪去。 ” LJJ:“我可以離線嗎?” SHY:“可以,每次操作是不加密的,” LJJ:“我可以暴力嗎?” SHY:“自重” LJJ很鬱悶,你能幫幫他嗎
第一行有一個正整數m,表示操作個數。 接下來m行,每行先給出1個正整數c。 若c=1,之後一個正整數x,表示新建一個權值為x的節點,並且節點編號為n+1(當前有n個節點)。 若c=2,之後兩個正整數a,b,表示在a,b之間連線一條邊。 若c=3,之後兩個正整數 a,x,表示a聯通快內原本權值小於x的節點全部變成x。 若c=4,之後兩個正整數a,x,表示a聯通快內原本權值大於x的節點全部變成x。 若c=5,之後兩個正整數a,k,表示詢問a所屬於的聯通塊內的第k小的權值是多少。 若c=6,之後兩個正整數a,b,表示詢問a所屬聯通 快內所有節點權值之積與b所屬聯通快內所有節點權值之積的大小, 若a所屬聯通快內所有節點權值之積大於b所屬聯通快內所有節點權值之積,輸出1,否則為0。 若c=7,之後一個正整數a,表示詢問a所在聯通塊大小 若c=8,之後兩個正整數a,b,表示斷開a,b所連線 的邊。 若c=9,之後一個正整數a,表示斷開a點的所有連邊 具體輸出格式見樣例
對100%的資料 0<=m<=400000,c<=7,所有出現的數均<=1000000000,所有出現的點保證存在 【HINT】請認真閱讀題面
注:儘管不想說太多題外話然而 僅作紀念,願不忘處心,摘得明月 分析: KEY POINTS:平行資料結構的構建(Disjoint_set AND WSegmentTree) (事實上本題是一道相當好的資料結構練手題,涉及平行資料結構構建,資料放縮及其注意事項,並查集線段樹的應用(資料結構中的集合與元素),語法邏輯優化常數) 首先對這個問題做一個簡單的分析 1:建點(Disjoint-set)2:連邊(Disjoint-set)3:標準值向下(WSegmentTree)4:標準值向上(WSegmentTree)5:權值次序(WSegmentTree)6:比較權值之積(WSegmentTree)7:聯通塊大小(WSegmentTree)
1 #include<iostream> 2 #include<algorithm> 3 #include<cstdio> 4 #include<cmath> 5 using namespace std; 6 #define I int 7 #define D double 8 #define B bool 9 #define C char 10 #define RE register 11 #define V void 12 #define L inline 13 const I MAXN = 4e5 + 10; 14 I m,num,root[MAXN],cnt,auxiliary[MAXN]; 15 struct PRODUCT { I typ,a,b,x,k; }pro[MAXN]; 16 L I read(); 17 struct WSegmentTree{ 18 D product[MAXN * 20]; 19 I seg,sum[MAXN * 20],lc[MAXN * 20],rc[MAXN * 20]; 20 V pushup(I x){ sum[x] = sum[lc[x]] + sum[rc[x]]; product[x] = product[lc[x]] + product[rc[x]]; } 21 V insert(I &x,I l,I r,I pos,I val1,D val2){ 22 if(!x) x = ++seg; 23 if(l == r) { product[x] += val2,sum[x] += val1; return ; } 24 I mid = l + r >> 1; 25 pos <= mid ? insert(lc[x],l,mid,pos,val1,val2) : insert(rc[x],mid + 1,r,pos,val1,val2); 26 pushup(x); 27 } 28 I search(I x,I l,I r,I order){ 29 if(!x || sum[x] < order) return 0; 30 if(l == r) return auxiliary[l]; 31 I mid = l + r >> 1; 32 return order <= sum[lc[x]] ? search(lc[x],l,mid,order) : search(rc[x],mid + 1,r,order - sum[lc[x]]); 33 } 34 I del(I x,I l,I r,I ql,I qr){ 35 I res(0); 36 if(!sum[x]) return 0; 37 if(l == r) { res += sum[x],sum[x] = product[x] = 0; return res; } 38 I mid = l + r >> 1; 39 if(ql <= mid) res += del(lc[x],l,mid,ql,qr); 40 if(qr > mid) res += del(rc[x],mid + 1,r,ql,qr); 41 pushup(x); 42 return res; 43 } 44 I merge(I x,I y,I l,I r){ 45 if(!x || !y) return x + y; 46 if(l == r){ 47 sum[x] += sum[y]; 48 product[x] += product[y]; 49 return x; 50 } 51 I mid = l + r >> 1; 52 lc[x] = merge(lc[x],lc[y],l,mid); 53 rc[x] = merge(rc[x],rc[y],mid + 1,r); 54 pushup(x); 55 return x; 56 } 57 }WSegmentTree; 58 struct Disjoint_set{ 59 I father[MAXN]; 60 I get(I x) { return x == father[x] ? x : father[x] = get(father[x]); } 61 V merge(I x,I y){ 62 I fx = get(x); I fy = get(y); 63 if(fx != fy){ father[fy] = fx; root[fx] = WSegmentTree.merge(root[fx],root[fy],1,cnt); } 64 } 65 }Disjoint_set; 66 signed main(){ 67 m = read(); 68 for(RE I i(1);i <= m; ++ i){ 69 pro[i].typ = read(); 70 if(pro[i].typ == 1) pro[i].x = read(); 71 if(pro[i].typ == 2) pro[i].a = read(),pro[i].b = read(); 72 if(pro[i].typ == 3) pro[i].a = read(),pro[i].x = read(); 73 if(pro[i].typ == 4) pro[i].a = read(),pro[i].x = read(); 74 if(pro[i].typ == 5) pro[i].a = read(),pro[i].k = read(); 75 if(pro[i].typ == 6) pro[i].a = read(),pro[i].b = read(); 76 if(pro[i].typ == 7) pro[i].a = read(); 77 } 78 for(RE I i(1);i <= m; ++ i) 79 if(pro[i].typ == 1 || pro[i].typ == 3 || pro[i].typ == 4) auxiliary[++cnt] = pro[i].x; 80 sort(auxiliary + 1,auxiliary + cnt + 1); 81 cnt = unique(auxiliary + 1,auxiliary + cnt + 1) - auxiliary - 1; 82 for(RE I i(1);i <= m; ++ i) 83 if(pro[i].typ == 1 || pro[i].typ == 3 || pro[i].typ == 4) pro[i].x = lower_bound(auxiliary + 1,auxiliary + cnt + 1,pro[i].x) - auxiliary; 84 for(RE I i(1);i <= m; ++ i){ 85 if(pro[i].typ == 1) Disjoint_set.father[++num] = num,WSegmentTree.insert(root[num],1,cnt,pro[i].x,1,log(auxiliary[pro[i].x])); 86 if(pro[i].typ == 2) Disjoint_set.merge(pro[i].a,pro[i].b); 87 if(pro[i].typ == 3) { I f = Disjoint_set.get(pro[i].a); I tmp = WSegmentTree.del(root[f],1,cnt,1,pro[i].x - 1); 88 if(tmp) WSegmentTree.insert(root[f],1,cnt,pro[i].x,tmp,(D)tmp * log(auxiliary[pro[i].x])); 89 } 90 if(pro[i].typ == 4) { I f = Disjoint_set.get(pro[i].a); I tmp = WSegmentTree.del(root[f],1,cnt,pro[i].x + 1,cnt); 91 if(tmp) WSegmentTree.insert(root[f],1,cnt,pro[i].x,tmp,(D)tmp * log(auxiliary[pro[i].x])); 92 } 93 if(pro[i].typ == 5) { I f = Disjoint_set.get(pro[i].a); 94 printf("%d\n",WSegmentTree.search(root[f],1,cnt,pro[i].k)); 95 } 96 if(pro[i].typ == 6) { I f1 = Disjoint_set.get(pro[i].a); I f2 = Disjoint_set.get(pro[i].b); 97 if(WSegmentTree.product[root[f1]] > WSegmentTree.product[root[f2]]) puts("1"); 98 else puts("0"); 99 } 100 if(pro[i].typ == 7) { I f = Disjoint_set.get(pro[i].a); 101 printf("%d\n",WSegmentTree.sum[root[f]]); 102 } 103 } 104 } 105 L I read(){RE I x(0);RE C z(getchar());while(!isdigit(z))z=getchar();while(isdigit(z))x=(x<<3)+(x<<1)+(z^48),z=getchar();return x;}View Code
問題一:平行資料結構構建(本題主要是Disjoint_set及WSegmentTree)
考慮程式碼中這兩者的關係(同構同算)呈現平行關係,這啟發我們儘管完全是不同型別的資料結構,但我們可以主觀意義上將多種資料結構合併作為一個大資料結構來解決問題
更通俗的說就是當僅使用一種資料結構的特性難以解決較為複雜的問題時,可以將多種資料結構的特性結合使用解決問題
注意:這種多資料結構合併手段因其客觀主體並非一個完整的資料結構,因此我們在對其操作時必須保證其內部各個資料結構進行的是平行操作,這樣才不會出現混亂
問題二:資料放縮及注意事項
本題中較難解決的是6比較權值之積,資料範圍long long也無法承受,然而考慮關鍵字“比較”,這啟發我們可以類似HASH思想/離散化思想,對資料結構進行放縮(log)解決問題
然而本人在比較是出現了精度問題,原因在於對放縮資料的選擇:本人在離散化後將離散化資料再次進行放縮,儘管這樣會使資料縮小到一定程度,但是要考慮精度問題(當資料很小,進行log操作
並比較時由於計算機的自動舍位會使比較出錯),當然可以寫精度函式解決,但是如果直接log原始資料會更方便
問題三:並查集線段樹及其應用
並查集在本題中主要應用在確定代表元(根節點)上,也就是說對於這種樹狀資料結構進行維護時,一定要對根節點進行操作,否則會造成錯誤
線段樹在本題中則是維護了具體資料,這裡小總結線段樹的基本構成:線段樹上每個節點都代表了一個區間(可以實參可以傳參),並且記錄了對應區間的一定資訊,我們在操作時
是通過樹上節點的索引找到目標區間位置來進行操作
問題四:語法邏輯優化常數
函式中引用函式時要注意時間上的先後順序,避免多個函式相互影響的發生
由於函式呼叫需要一定時間,因此在多次引用時可以類似記憶化,記錄函式值進行操作
注:在一定情況下可以將多個函式合併,當然這些函式的返回值不能衝突,也要保證邏輯關係