「专题总结」LCT 2
差不多理解板子之后,写了一些奇怪的题。
但是还是那个问题:树剖真好使。
魔法森林:mikufun说这个是傻逼题。
为了得到书法大家的真传,小 E 同学下定决心去拜访住在魔法森林中的隐士。
魔法森林可以被看成一个包含n个节点m条边的无向图,节点标号为1~n,边标号为1~m。
初始时小 E 同学在号节点 ,隐士则住在n号节点。小 E 需要通过这一片魔法森林,才能够拜访到隐士。
魔法森林中居住了一些妖怪。每当有人经过一条边的时候,这条边上的妖怪就会对其发起攻击。
幸运的是,在1号节点住着两种守护精灵:A型守护精灵与B型守护精灵。小 E 可以借助它们的力量,达到自己的目的。
只要小 E 带上足够多的守护精灵,妖怪们就不会发起攻击了。具体来说,无向图中的每一条边包含两个权值$A_i$与$B_i$。
若身上携带的 A 型守护精灵个数不少于$A_i$,且B型守护精灵个数不少$B_i$,这条边上的妖怪就不会对通过这条边的人发起攻击。
当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向小 E 发起攻击,他才能成功找到隐士。
由于携带守护精灵是一件非常麻烦的事,小 E 想要知道,要能够成功拜访到隐士,最少需要携带守护精灵的总个数。
守护精灵的总个数为A型守护精灵的个数与B型守护精灵的个数之和。$n \le 50000,m \le 100000$
只有两个参数,肯定有点好YY的吧。
挺套路的,一维排序,然后就会好做的多吧。
所以边按照A排序,维护最小生成树,然后不断加入B小A大的边更新答案。
类似于水管局长。也是那种「替代生成树上最大边」的思路。不过是最值。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 150005 4 #define lc c[p][0] 5 #define rc c[p][1] 6 struct edge{ 7 int a,b,x,y; 8 friend bool operator<(edge p,edge q){return p.a<q.a;} 9 }E[S]; 10 int f[S],n,m,c[S][2],w[S],P[S],lz[S],s[S],ans=998244353,k[S]; 11 int get(int p){return p>n?E[p-n].b:-998244353;} 12 void up(int p){ 13 if(w[lc]>w[rc])w[p]=w[lc],P[p]=P[lc];else w[p]=w[rc],P[p]=P[rc]; 14 if(get(p)>w[p])w[p]=get(p),P[p]=p;//printf("%d %d %d %d %d\n",p,w[p],lc,rc,get(p)); 15 } 16 void rev(int p){lc^=rc^=lc^=rc;lz[p]^=1;} 17 void down(int p){if(lz[p])rev(lc),rev(rc),lz[p]=0;} 18 bool nr(int p){return c[f[p]][0]==p||c[f[p]][1]==p;} 19 void rotate(int p){ 20 int fa=f[p],gr=f[fa],dir=c[fa][1]==p,br=c[p][!dir]; 21 c[fa][dir]=br;c[p][!dir]=fa;if(nr(fa))c[gr][c[gr][1]==fa]=p; 22 f[p]=gr;f[fa]=p;f[br]=fa;up(fa); 23 } 24 void splay(int p){int tp=0,fa,gr;s[++tp]=p; 25 for(int r=p;nr(r);s[++tp]=r=f[r]);for(;tp;down(s[tp--])); 26 while(nr(p)){fa=f[p],gr=f[fa]; 27 if(nr(fa))rotate(c[gr][1]==fa^c[fa][1]==p?fa:p);rotate(p); 28 }up(p); 29 } 30 void access(int p){for(int r=0;p;p=f[r=p])splay(p),rc=r,up(p);} 31 void make(int p){access(p);splay(p);rev(p);} 32 void split(int x,int y){make(x);access(y);splay(y);} 33 void link(int x,int y){make(x);f[x]=y;} 34 void cut(int x,int y){split(x,y);c[y][0]=f[x]=0;up(y);} 35 int F(int p){return p==k[p]?p:k[p]=F(k[p]);} 36 int main(){ 37 scanf("%d%d",&n,&m);w[0]=-998244353; 38 for(int i=1;i<=n;++i)k[i]=i; 39 for(int i=1;i<=m;++i)scanf("%d%d%d%d",&E[i].x,&E[i].y,&E[i].a,&E[i].b); 40 sort(E+1,E+1+m);for(int i=1;i<=n+m;++i)up(i);int i=1,x,y; 41 for(int i=1,p;i<=m;++i){x=E[i].x,y=E[i].y; 42 if(F(x)!=F(y)){k[F(x)]=F(y);link(x,i+n);link(y,i+n);goto cal;} 43 split(x,y);if(w[y]<E[i].b)continue;p=P[y]-n; 44 cut(p+n,E[p].x);cut(p+n,E[p].y);link(i+n,E[i].x);link(i+n,E[i].y); 45 cal: if(F(1)==F(n))split(n,1),ans=min(ans,E[i].a+w[1]); 46 }printf("%d\n",ans==998244353?-1:ans); 47 }
大森林:
小 Y 家里有一个大森林,里面有n棵树,编号从1到n 。
一开始这些树都只是树苗,只有一个节点,标号为1。这些树都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力。
小 Y 掌握了一种魔法,能让第l棵树到第r棵树的生长节点长出一个子节点。同时她还能修改第l棵树到第r棵树的生长节点。
她告诉了你她使用魔法的记录,你能不能管理她家的森林,并且回答她的询问某树上两点距离呢?
$n \le 10^5,m \le 2 \times 10^5$
大神题。需要对裸的LCT进行一些YY与改动。
这题不强制在线,所以优先考虑离线(要养成这种习惯)
有一些小的结论,不是很好自己YY出来。
首先,如果我们把所有的加节点操作的范围都视为1到n而不是l到r,其实并不会对答案产生影响。
当然,有些树不会长出这个节点,那么以后的换生长节点的操作就无效了,其实它也就是限制了换生长节点的操作的左右范围,然后l和r就没用了。
因为保证询问的是存在的节点,后续操作都是,所以并不会对询问产生影响。
其次,询问都放在最后,并不会产生影响。因为每次你只是加点并不改变原有的树形态,答案不会变。
这两个结论充满了离线的气息。对的。现在题目中原有的「时间」概念已经差不多没用了。
让我们干脆废掉这一维吧:还可以发现,对于所有的修改生长节点的操作,它之后直到下一个修改生长节点的操作,中间过程中的所有新长的节点,要么挂在了这个生长节点下面,要么就无视这个操作挂在了原来应该挂的节点下面。所以之间的每个生长操作都是无序的。
但是有更加麻烦的另一维:n棵树。对多棵树的操作我们肯定不会,而对单棵树还是可以的。
所以我们还需要那种类似于差分的思路。从左往右扫所有树,只在修改区间两个端点进行修改操作,这样我们就能得到每棵树的具体形态了。
还要用到一个大套路:建虚点。
对于每个换生长节点的操作,我们建立一个虚点,直到下次换生长节点的操作之前,过程中所有的长叶子操作都长到这个虚点之下。
而我们对所有的虚点,按时间顺序,从前往后依次连边,这就是初始状态。先管它叫虚链吧。
然后把所有长的叶子都挂在对应的虚点上,可以发现这个树除了多了几个虚点以外,树的形态和没有修改生长节点的操作是相同的。
于是对于某一个操作区间,在我们从左往右扫每棵树的时候,我们就把这个虚点,带着中间子树里所有新长的叶子,都移动到新的生长节点上。
等操作的右端点到了,也就是要撤销了,那么就把这个虚点再摘下来放回虚链上。
这样就能时刻维护出我们需要的树的形态了。就是Cut和Link了。
然而比较特殊的是,这道题不能用直接split,否则答案不对。例如一个2和3的父亲都是1,而因为虚点的存在,有可能1,2,3都与虚点相连。
这样你查询的时候2,3的lca是虚点而路径上不存在1,答案就错了。
然后如果非得make_root的话操作会麻烦一些,下面会讲到。所以不建议make_root。
所以说link和cut函数都有改动,而且询问时不能split来问了。
先说link和cut的变化。可以发现所有link操作直接接上去就好了,而cut操作每次调用时一定是断掉了某个点与它父亲之间的边。
于是就access后splay,断开它与左子树的关系就实现了cut。
最后是询问,问当前树上两点距离。你的树上有实际上并不存在的虚点,它们并不会对答案产生影响。
所以我们给每个实点附上1的权,虚点权为0。
现在需要的就是求两点之间有多少实点。但是因为不能make_root和split所以会麻烦一些。
考虑树上差分,dis(a,b)=dep(a)+dep(b)-2dep(lca)
求深度倒好说。因为始终没有make_root那么根一直都是1,只需要access一下就能提出1到p的链,然后splay完就可以直接询问了。
如果你其它make_root了,那就必须在每次询问前都要make_root(1)比较麻烦。
怎么求lca?我们考虑已有函数的过程:access。它会不断打通一个点到根的路径,我们会先调用access(a),然后是access(b)
在access(b)的过程中,因为你在access(a)的时候已经打通了lca到1的路径,而你再access(b)的话就不需要再打通这一段了。
所以,lca就是access(b)时最后一次打通的点。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 400005 4 #define mp make_pair 5 #define lc c[p][0] 6 #define rc c[p][1] 7 vector<pair<pair<int,int>,int>>M[S]; 8 vector<pair<int,int>>v[S]; 9 int n,m,pc=1,ans[S],qc,L[S],R[S],ic,to[S],lst[S],nw=1,w[S],c[S][2],f[S],q[S],V[S]; 10 bool nr(int p){return c[f[p]][0]==p||c[f[p]][1]==p;} 11 void up(int p){w[p]=w[lc]+w[rc]+V[p];} 12 void rotate(int p){ 13 int fa=f[p],gr=f[fa],dir=c[fa][1]==p,br=c[p][!dir]; 14 c[fa][dir]=br;c[p][!dir]=fa;if(nr(fa))c[gr][c[gr][1]==fa]=p; 15 f[f[f[br]=fa]=p]=gr;up(fa);up(p); 16 } 17 void splay(int p){ 18 int fa,gr; 19 while(nr(p)){ 20 fa=f[p],gr=f[fa]; if(nr(fa))rotate(c[fa][1]==p^c[gr][0]==fa?fa:p); 21 rotate(p); 22 }up(p); 23 } 24 int access(int p,int r=0){for(;p;p=f[r=p])splay(p),rc=r,up(p);return r;} 25 void link(int x,int y){splay(x);f[x]=y;} 26 void cut(int p){access(p);splay(p);f[lc]=0,lc=0;up(p);} 27 int query(pair<int,int>p,int opt){ 28 int u=p.first,v=p.second,ans,lca; 29 access(u);splay(u);ans=w[u]; 30 lca=access(v);splay(v),ans+=w[v]; 31 access(lca);splay(lca);ans-=w[lca]<<1; 32 return ans; 33 } 34 int main(){ 35 scanf("%d%d",&n,&m);ic=m; V[1]=1,R[1]=n; 36 for(int i=1,opt,l,r,u,x;i<=m;++i){ 37 scanf("%d",&opt); 38 if(opt==0)V[++pc]=1,scanf("%d%d",&L[pc],&R[pc]),link(pc,nw); 39 if(opt==1){ 40 lst[++ic]=nw,link(ic,nw),nw=ic,scanf("%d%d%d",&l,&r,&u),to[ic]=u; 41 l=max(l,L[u]);r=min(r,R[u])+1;if(r<=l)continue; 42 v[l].push_back(mp(1,ic));v[r].push_back(mp(0,ic)); 43 } 44 if(opt==2)scanf("%d%d%d",&l,&u,&x),M[l].push_back(mp(mp(u,x),++qc)); 45 } 46 for(int i=1,x;i<=n;++i){ 47 for(auto o:v[i]){x=o.second; 48 if(o.first) cut(x),link(x,to[x]); 49 else cut(x),link(x,lst[x]); 50 } 51 for(auto it:M[i]) ans[it.second]=query(it.first,i==81&&it.second==6); 52 } 53 for(int i=1;i<=qc;++i)printf("%d\n",ans[i]); 54 }
情报传递:mikufun说这个是傻逼题。
奈特公司是一个巨大的情报公司,它有着庞大的情报网络。情报网络中共有 名情报员。每名情报员可能有若干名(可能没有)下线,除1名大头目外其余每名情报员有且仅有1名上线。
奈特公司纪律森严,每名情报员只能与自己的上、下线联系,同时,情报网络中仟意两名情报员一定能够通过情报网络传递情报。
奈特公司每天会派发以下两种任务中的一个任务:
- 搜集情报:指派x号情报员搜集情报;
- 传递情报:将一条情报从x号情报员传递给y号情报员。
情报员最初处于潜伏阶段,他们是相对安全的,我们认为此时所有情报员的危险值为0;一旦某个情报员开始搜集情报,他的危险值就会持续增加,每天增加1点危险值
(开始搜集情报的当天危险值仍为0,第2天危险值为1,第3天危险值2,以此类推)。传递情报并不会使情报员的危险值增加。
为了保证传递情报的过程相对安全,每条情报都有一个风险控制值C。奈特公司认为,参与传递这条情报的所有情报员中,危险值大于C的情报员将对该条情报构成威胁。
现在,奈特公司希望知道,对于每个传递情报任务,参与传递的情报员有多少个,其中对该条情报构成威胁的情报员有多少个。
首先直接维护一堆在变化的点肯定是困难的,所以我们每次询问的其实是有多少个情报员在(当前时间-C)之前开始搜集情报了。
没有强制在线,考虑离线。
既然你问的是之前有多少情报员已经开始搜集,那么我干脆就在那个时间问就好啊,从(当前时间-C)到当前时间之内的所有操作对答案没有影响。
所以现在操作就变成了:单点赋值为1,查询链和。
没有link没有cut,然后这种东西可以用树剖维护了。常数也小一些。
这么想下来的确比较简单,但是如果没想到离线那就歇比了。
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 #define S 200005 5 #define md (cl+cr>>1) 6 int w[S],n,fir[S],l[S],to[S],dfn[S],f[S],sz[S],hson[S],t,top[S],ec,m,dep[S],ans1[S],ans2[S],qc; 7 struct qs{int t,opt,x,y,o;friend bool operator<(qs a,qs b){return a.t<b.t||(a.t==b.t&&!a.opt);}}q[S]; 8 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;} 9 void add(int p){for(;p<=n;p+=p&-p)w[p]++;} 10 int ask(int p,int a=0){for(;p;p^=p&-p)a+=w[p];return a;} 11 void dfs(int p,int fa){f[p]=fa;sz[p]=1; 12 for(int i=fir[p];i;i=l[i]){ 13 dfs(to[i],p);sz[p]+=sz[to[i]]; 14 if(sz[to[i]]>sz[hson[p]])hson[p]=to[i]; 15 } 16 } 17 void DFS(int p,int tp){ 18 dfn[p]=++t;dep[p]=dep[f[p]]+1;top[p]=tp; 19 if(hson[p])DFS(hson[p],tp); 20 for(int i=fir[p];i;i=l[i])if(!dfn[to[i]])DFS(to[i],to[i]); 21 } 22 int lca(int x,int y){ 23 while(top[x]!=top[y])if(dep[top[x]]>dep[top[y]])x=f[top[x]];else y=f[top[y]]; 24 return dep[x]>dep[y]?y:x; 25 } 26 int query(int p,int a=0){while(p)a+=ask(dfn[p])-ask(dfn[top[p]]-1),p=f[top[p]];return a;} 27 int main(){ 28 scanf("%d%*d",&n); 29 for(int i=2,x;i<=n;++i)scanf("%d",&x),link(x,i); 30 dfs(1,0);DFS(1,1); 31 scanf("%d",&m); 32 for(int i=1,c;i<=m;++i){ 33 scanf("%d%d",&q[i].opt,&q[i].x);q[i].opt--;q[i].t=i; 34 if(!q[i].opt)scanf("%d%d",&q[i].y,&c),q[i].t-=c,q[i].o=++qc; 35 }sort(q+1,q+1+m); 36 for(int i=1,L,x,y;i<=m;++i)if(q[i].opt)add(dfn[q[i].x]); 37 else x=q[i].x,y=q[i].y,L=lca(x,y),ans1[q[i].o]=dep[x]+dep[y]-dep[L]*2+1,ans2[q[i].o]=query(x)+query(y)-query(L)-query(f[L]); 38 for(int i=1;i<=qc;++i)printf("%d %d\n",ans1[i],ans2[i]); 39 }
在美妙的数学王国中畅游:
数字和数学规律主宰着这个世界。
机器的运转,生命的消长,宇宙的进程,这些神秘而又美妙的过程无不可以用数学的语言展现出来。
这印证了一句古老的名言:“学好数理化,走遍天下都不怕。”
学渣小R被大学的数学课程虐得生活不能自理,微积分的成绩曾是他在教室里上的课的最低分。然而他的某位陈姓室友却能轻松地在数学考试中得到满分。
为了提升自己的数学课成绩,有一天晚上(在他睡觉的时候),他来到了数学王国。
数学王国中,每个人的智商可以用一个属于[0,1]
的实数表示。数学王国中有 个城市,编号从 到 ,这些城市由若干座魔法桥连接。
每个城市的中心都有一个魔法球,每个魔法球中藏有一道数学题。每个人在做完这道数学题之后都会得到一个在 区间内的分数。一道题可以用一个从x映射到f(x)的函数表示。
若一个人的智商为x,则他做完这道数学题之后会得到f(x)分。函数有三种形式:
正弦函数:sin(ax+b)
指数函数:e^{ax+b}
一次函数:ax+b
数学王国中的魔法桥会发生变化,有时会有一座魔法桥消失,有时会有一座魔法桥出现。
但在任意时刻,只存在至多一条连接任意两个城市的简单路径(即所有城市形成一个森林)。在初始情况下,数学王国中不存在任何的魔法桥。
数学王国的国王拉格朗日很乐意传授小R数学知识,但前提是小R要先回答国王的问题。
这些问题具有相同的形式,即一个智商为x的人从城市u旅行到城市v(即经过u到v这条路径上的所有城市,包括u和v)且做了所有城市内的数学题后,他所有得分的总和是多少。
$n \le 10^5,m \le 2 \times 10^5$。相对或绝对精度$10^{-7}$。不联通输出unreachable
lct是很显然了。link和cut都有了,而且很善良都保证合法。
但是询问链上函数和这种东西的确没见过。
这题是有spj的,浮点数精度误差可以接受。
所以我们去大致拟合一下原函数,用一种能相加的形式,就可以在精度范围内维护链上函数和了。
最后根据查询得到的链上多项式之和,带入x就能得到解。
问题就在于如何把指数函数和三角函数转化成多项式形式。
泰勒展开。或者说,麦克劳林公式。把x=0代入就可以展开为多项式形式。
然后就没了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define lc c[p][0] 4 #define rc c[p][1] 5 #define S 100005 6 #define D double 7 int f[S],c[S][2],lz[S],q[S],n,m;D fac[11],v[S][11],w[S][11];char o[12]; 8 bool not_root(int p){return c[f[p]][0]==p||c[f[p]][1]==p;} 9 void rev(int p){swap(lc,rc);lz[p]^=1;} 10 void down(int p){if(lz[p])rev(lc),rev(rc),lz[p]=0;} 11 void up(int p){for(int i=0;i<=10;++i)w[p][i]=v[p][i]+w[lc][i]+w[rc][i];} 12 void rotate(int p){ 13 int fa=f[p],gr=f[fa],dir=c[fa][1]==p,br=c[p][!dir]; 14 c[p][!dir]=fa;c[fa][dir]=br;if(not_root(fa))c[gr][c[gr][1]==fa]=p; 15 f[br]=fa;f[fa]=p;f[p]=gr;up(fa); 16 } 17 void splay(int p){ 18 int top=0,fa,gr;q[++top]=p; 19 for(int r=p;not_root(r);q[++top]=r=f[r]);for(;top;down(q[top--])); 20 while(not_root(p)){fa=f[p];gr=f[fa]; 21 if(not_root(fa))rotate(c[fa][1]==p^c[gr][1]==fa?fa:p);rotate(p); 22 }up(p); 23 } 24 void access(int p){for(int r=0;p;p=f[r=p])splay(p),rc=r,up(p);} 25 void make(int p){access(p);splay(p);rev(p);} 26 int find(int p){access(p);splay(p);for(;lc;p=lc);splay(p);return p;} 27 void split(int x,int y){make(x);access(y);splay(y);} 28 void link(int x,int y){make(x);f[x]=y;} 29 void cut(int x,int y){split(x,y);f[x]=c[y][0]=0;up(y);} 30 void chg(int p,int k,D a,D b){ 31 if(k==1)for(int i=0;i<=10;++i)v[p][i]=(i&1?cos(b):sin(b))*pow(a,i)*(i>>1&1?-1:1)/fac[i]; 32 if(k==2)for(int i=0;i<=10;++i)v[p][i]=pow(a,i)*exp(b)/fac[i]; 33 if(k==3){for(int i=2;i<=10;++i)v[p][i]=0;v[p][0]=b;v[p][1]=a;} 34 } 35 D cal(int p,D x,D a=0){for(int i=0;i<=10;++i)a+=w[p][i]*pow(x,i);return a;} 36 int main(){ 37 scanf("%d%d%*s",&n,&m);D a,b; 38 fac[0]=1;for(int i=1;i<=10;++i)fac[i]=fac[i-1]*i; 39 for(int i=1,k;i<=n;++i)scanf("%d%lf%lf",&k,&a,&b),chg(i,k,a,b); 40 for(int i=1,x,y;i<=m;++i){ 41 scanf("%s%d%d",o,&x,&y);x++;y++; 42 if(o[0]=='a')link(x,y); 43 if(o[0]=='t'){scanf("%lf",&a);if(find(x)!=find(y))puts("unreachable");else split(x,y),printf("%.8lf\n",cal(y,a));} 44 if(o[0]=='d')cut(x,y); 45 if(o[0]=='m')y--,scanf("%lf%lf",&a,&b),make(x),chg(x,y,a,b),up(x); 46 } 47 }
LCA:mikufun说这个确实是傻逼题。
给出一个n个节点的有根树(编号为1到n,根节点为1)。一个点的深度定义为这个节点到根的距离+1。
设dep(i)表示点i的深度,lca(i,j)表示i与j 的最近公共祖先。
有q次询问,每次询问给出l,r,x,求$\sum\limits_{i=l}^{r} dep(lca(x,i))$
$n \le 50000,q \le 50000$
还是那句话,不强制在线,优先考虑离线。
题目中给出的式子往往是不能直接算的,深度这个东西直接弄还是不方便。
根据定义,其实深度就是一个点到根的路径上点的个数。(废话,但是有用)
比较讨厌的是l和r这个限制,我们先强制所有的l=1,看现在能不能做了。
那么我们就可以把所有的询问按照r排序,然后依次考虑每个点,到了哪个点就把它到根路径上的点的权值都+1。
然后处理询问时,就询问x到根的点权和即可。
正确性好说:考虑一个点对的贡献,x把它到根的所有点+1了,y查询它到根,那么只有在lca以上的部分会被查询到。
所以现在我们已经会l=1了。当然这个操作是可减的所以我们做一下差分把询问拆成一加一减就可以了。
维护这种操作,树链剖分足够。
其实也可以在线,只要把线段树改成主席树就可以。
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 #define S 50005 5 #define mod 201314 6 int n,m,b1[S],b2[S],dfn[S],sz[S],top[S],l[S],fir[S],to[S],ec,f[S],hson[S],t,ans[S]; 7 struct Q{int x,o,k,p;friend bool operator<(Q x,Q y){return x.x<y.x;}}q[S<<1]; 8 void add(int *a,int p,int v){for(;p<=n;p+=p&-p)a[p]=(a[p]+v)%mod;} 9 int ask(int *a,int p,int w=0){for(;p;p^=p&-p)w=(w+a[p])%mod;return w;} 10 void add(int l,int r){add(b1,l,1);add(b1,r+1,-1);add(b2,l,l-1);add(b2,r+1,-r);} 11 int query(int l,int r){return ask(b2,l-1)-ask(b1,l-1)*(l-1)-ask(b2,r)+ask(b1,r)*r;} 12 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;} 13 void dfs(int p,int fa){ 14 sz[p]=1;f[p]=fa; 15 for(int i=fir[p];i;i=l[i])if(to[i]!=fa){ 16 dfs(to[i],p);sz[p]+=sz[to[i]]; 17 if(sz[to[i]]>sz[hson[p]])hson[p]=to[i]; 18 } 19 } 20 void DFS(int p,int tp){ 21 top[p]=tp;dfn[p]=++t; 22 if(hson[p])DFS(hson[p],tp); 23 for(int i=fir[p];i;i=l[i])if(!top[to[i]])DFS(to[i],to[i]); 24 } 25 void chg(int p){for(;p;p=f[top[p]])add(dfn[top[p]],dfn[p]);} 26 int get(int p,int w=0){for(;p;p=f[top[p]])w+=query(dfn[top[p]],dfn[p]);return w%mod;} 27 main(){ 28 scanf("%d%d",&n,&m); 29 for(int i=2,x;i<=n;++i)scanf("%d",&x),link(x+1,i); 30 dfs(1,0);DFS(1,1); 31 for(int i=1;i<=m;++i)scanf("%d%d%d",&q[i].x,&q[i+m].x,&q[i].p),q[i].p++, 32 q[i+m].x++,q[i].o=q[i+m].o=i,q[i].k=-1,q[i+m].k=1,q[i+m].p=q[i].p; 33 sort(q+1,q+1+m+m);q[m+1+m].x=1+n;int ptr=1;while(!q[ptr].x)ptr++; 34 for(int i=1;i<=n;++i){ 35 chg(i); 36 while(q[ptr].x==i)ans[q[ptr].o]+=q[ptr].k*get(q[ptr].p),ptr++; 37 }for(int i=1;i<=m;++i)printf("%d\n",(ans[i]%mod+mod)%mod); 38 }
即时战略:
小 M 在玩一个即时战略 (Real Time Strategy) 游戏。不同于大多数同类游戏,这个游戏的地图是树形的。也就是说,地图可以用一个由n个结点,n-1条边构成的连通图来表示。
每个结点有两种可能的状态:「已知的」或「未知的」。游戏开始时,只有1号结点是已知的。
在游戏的过程中,小 M 可以尝试探索更多的结点。具体来说,小 M 每次操作时需要选择一个已知的结点 ,和一个不同于x的任意结点y(结点y可以是未知的)。然后游戏的自动寻路系统会给出 到 的最短路径上的第二个结点z,也就是从x走到y的最短路径上与x相邻的结点。
此时,如果结点z是未知的,小 M 会将它标记为已知的。
这个游戏的目标是:利用至多T次探索操作,让所有结点的状态都成为已知的。然而小 M 还是这个游戏的新手,她希望得到你的帮助。
第一道交互题。思路清奇。
仔细观察数据范围:对于Datatype=3,T=n+log n。否则,T=n log n。
那就先考虑链吧。先假设1为链端。那还有啥啊一个劲找未知的点探下去就好了啊。
但是如果1不是链端,那你就有一个已知线段,不断向两边延伸。还是随机挑点,用右端点explore,如果已知就从左端点继续explore,如果未知就继续探下去。
每次都期望把未知部分缩短一半,所以期望失配次数就是log级别的。但是注意不要有任何多余的explore,常数卡的挺紧的。
DataType3必须特判,不然用下面的方法过不掉。
对于完全二叉树,那么就是从根,找到一个未知点,一路探过去就好了。
但是对于普通的数怎么办?在这种题里查询次数是nlog的,那么猜想就是二分了。
咋二分啊?
还是随机一个n排列依次探索未知点,不过这次要用lct维护一下。
这次利用的是splay上二分。很奇怪啊。假如我们现在在某一个splay的根,我们要探索to节点。
于是我们考虑所有explore的结果:
1,查询到的点未知:那么一路探过去就行。
2,查询到的点是当前点的前驱:那么就是你还要沿着实链走。前驱就是实链上深度大1的点。于是你应该继续沿着实链走一点,于是你从当前点走到splay上的左儿子继续该操作。
3,后继同理:也是利用二叉搜索树的性质去逼近to点,这两条就是所谓的splay上二分。
4,查询到的点不再当前splay上:那就跑到那个splay的根上继续该操作就好了。
每次探索到一个未知点的时候都把它link进来。要定期access保证实链的长度足够。
不能access错地方,否则询问次数的常数太大过不去Extra Test。
不能不access,否则你就是n棵splay了。。。
1 #include<bits/stdc++.h> 2 #include"rts.h" 3 using namespace std; 4 #define S 300005 5 #define lc c[p][0] 6 #define rc c[p][1] 7 int lz[S],c[S][2],f[S],q[S],v[S],p[S]; 8 bool nr(int p){return c[f[p]][0]==p||c[f[p]][1]==p;} 9 void rotate(int p){ 10 int fa=f[p],gr=f[fa],dir=c[fa][1]==p,br=c[p][!dir]; 11 c[p][!dir]=fa;c[fa][dir]=br;if(nr(fa))c[gr][c[gr][1]==fa]=p; 12 f[p]=gr;f[br]=fa;f[fa]=p; 13 } 14 void splay(int p){ 15 while(nr(p)){ 16 int fa=f[p],gr=f[fa]; 17 if(nr(fa))rotate(c[fa][1]==p^c[gr][1]==fa?fa:p); rotate(p); 18 } 19 } 20 void access(int p){for(int r=0;p;p=f[r=p])splay(p),rc=r;} 21 void link(int x,int y){f[y]=x;} 22 int find(int p){while(c[f[p]][0]==p||c[f[p]][1]==p)p=f[p];return p;} 23 int pre(int p){p=c[p][0];while(c[p][1])p=c[p][1];return p;} 24 int suf(int p){p=c[p][1];while(c[p][0])p=c[p][0];return p;} 25 void extend(int to){ 26 int np=find(1); 27 while(np!=to){ 28 int sec=explore(np,to); 29 if(!v[sec])v[sec]=1,link(np,sec),np=sec; 30 else if(sec==pre(np))np=c[np][0]; 31 else if(sec==suf(np))np=c[np][1]; 32 else np=find(sec); 33 }access(to); 34 } 35 void extend(int&p,int to){while(p!=to)v[p=explore(p,to)]=1;} 36 void play(int n,int T,int d){srand(time(0)); 37 v[1]=1;for(int i=2;i<=n;++i)p[i]=i; 38 random_shuffle(p+2,p+n+1); 39 if(d==3){for(int i=2,l=1,r=1,k;i<=n;++i)if(!v[p[i]])extend(v[k=explore(r,p[i])]?l:(v[k]=1,r=k),p[i]);} 40 else for(int i=2;i<=n;++i)if(!v[p[i]])extend(p[i]); 41 }
综上所述:mikufun是大神。