题解: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) 这是7个问题的简化与使用的基本数据结构 其中问题7既可以用线段树也可以用并查集维护 时间复杂度当然是并查集更优,而且所有问题都需要并查集的辅助来迅速确定代表元(根节点),当然这也是并查集的基本用途 首先粘上代码:(本题主要是细节以及一些思考问题,思路很简单,故直接进入正题)
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原始数据会更方便
问题三:并查集线段树及其应用
并查集在本题中主要应用在确定代表元(根节点)上,也就是说对于这种树状数据结构进行维护时,一定要对根节点进行操作,否则会造成错误
线段树在本题中则是维护了具体数据,这里小总结线段树的基本构成:线段树上每个节点都代表了一个区间(可以实参可以传参),并且记录了对应区间的一定信息,我们在操作时
是通过树上节点的索引找到目标区间位置来进行操作
问题四:语法逻辑优化常数
函数中引用函数时要注意时间上的先后顺序,避免多个函数相互影响的发生
由于函数调用需要一定时间,因此在多次引用时可以类似记忆化,记录函数值进行操作
注:在一定情况下可以将多个函数合并,当然这些函数的返回值不能冲突,也要保证逻辑关系
标签:LJJ,联通,快内,题解,WSegmentTree,BZOJ4399,权值,数据结构,节点 来源: https://www.cnblogs.com/HZOI-LYM/p/14881821.html