题意:这是一道交互题。交互库中有一棵树。一开始只有1节点已知。需要在T次询问内使得n个节点都已知。一次询问explore(x,y),返回从x到y路径上第一个点,并将返回点标记为已知。
数据有区分。
标程:
1 #include<cstdio> 2 #include<time.h> 3 #include<algorithm> 4 #include "rts.h" 5 using namespace std; 6 const int N=300005; 7 int son[N][2],fa[N],L[N],R[N],vis[N],E[2],id[N],now,nxt,it; 8 int is_rt(int x) {return son[fa[x]][0]!=x&&son[fa[x]][1]!=x;} 9 int ran() {return (int)(rand()/RAND_MAX+0.5);} 10 void up(int x) 11 { 12 L[x]=R[x]=x; 13 if (son[x][0]) L[x]=L[son[x][0]];//leftest 14 if (son[x][1]) R[x]=R[son[x][1]];//rightest 15 } 16 void rot(int x) 17 { 18 int y=fa[x],z=fa[y],l=(son[y][1]==x),r=l^1; 19 if (!is_rt(y)) son[z][(son[z][1]==y)]=x; 20 fa[x]=z;fa[y]=x;fa[son[x][r]]=y; 21 son[y][l]=son[x][r];son[x][r]=y; 22 up(y);up(x); 23 } 24 void spl(int x) 25 { 26 for (int y;!is_rt(x);rot(x)) 27 if (!is_rt(y=fa[x])) 28 if (son[y][0]==x^son[fa[y]][0]==y) rot(x);else rot(y); 29 } 30 int find_rt(int x) 31 { 32 for (;!is_rt(x);x=fa[x]); 33 return x; 34 } 35 void accs(int x) {for (int t=0;x;t=x,x=fa[x]) spl(x),son[x][1]=t,up(x);} 36 void ins(int x) 37 { 38 now=find_rt(1); 39 while (!vis[x]) 40 { 41 nxt=explore(now,x); 42 if (nxt==R[son[now][0]]) now=son[now][0];//father 43 else if (nxt==L[son[now][1]]) now=son[now][1];//son 44 else {//another splay 45 if (vis[nxt]) now=find_rt(nxt);//already have existed 46 else vis[nxt]=1,fa[nxt]=now,now=nxt;//add a new point 47 } 48 } 49 accs(x); 50 } 51 void play(int n,int T,int dataType) 52 { 53 srand(time(NULL)); 54 vis[1]=1; 55 for (int i=1;i<=n;i++) id[i]=i; 56 random_shuffle(id+2,id+n+1); 57 if (dataType==3)//chain 58 { 59 now=explore(1,id[2]);vis[now]=1; 60 E[0]=1;E[1]=now; 61 for (int i=2;i<=n;i++) 62 if (!vis[id[i]]) 63 { 64 it=ran();now=explore(E[it],id[i]);//注意explore的顺序(u->v) 65 if (!vis[now])//这一边 66 { 67 vis[E[it]=now]=1; 68 while (E[it]!=id[i]) E[it]=explore(E[it],id[i]),vis[E[it]]=1;//记得更改范围指针 69 }else {//另一边 70 it^=1; 71 while (E[it]!=id[i]) E[it]=explore(E[it],id[i]),vis[E[it]]=1; 72 } 73 } 74 return; 75 } 76 for (int i=1;i<=n;i++) L[i]=R[i]=i;//初始化 77 for (int i=2;i<=n;i++) 78 if (!vis[id[i]]) ins(id[i]); 79 }
易错点:1.注意lct之前的初始化L[i]=R[i]=i。并且不用每找到一个点就splay,新加入一个点access更新保证复杂度即可。
2.access不要写错,判定继续的条件应该是x而不是!is_rt(x)。
3.链的情况要更新边界。
技巧:可以用random_shuffle来避免一些数据的卡。
题解:lct/动态点分树
数据告诉我们:树T=nlogn,链T=n+logn.
树:
1.仿照CF772E的做法,可以通过点分来插入一个点。但不同的是,这不是一棵二叉树,在前向星上插入删除的复杂度太高,因此考虑动态点分树来支持动态加点。(然而我并不会写)
2.点分的思想也就是对树二分。因此我们树链剖分后在重链上二分也可以得到一样的效果。由于有加点,用lct即可。用splay维护每一条重链,因为splay的高度的log级别的,从根开始跳splay上的节点完成二分。讨论一下返回的已知点在询问点的儿子/父亲/另一个splay上->是否已知。最后access(x)更新树的形态。O(nlogn)。
链:
维护一段连续的已知点[L,R],对于一个未知点x,如果explore(L,x)是未知点,那么往R方向扩展,反之在L方向扩展。
每个点必定被explore一次O(n)。random第一次的询问是L还是R后,更改方向的次数期望是O(log n)或是说O(ln n)的。(证明:设E(n)表示扩展为长度为n的序列需要更改几次方向:E(n)=1+1/n*sigma(E[i])(i=[1,n-1])->E(n)-E(n-1)=1/n->E(n)=sigma(1/i)≈ln n)
所以总的是O(n+log n)。