「专题总结」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了。

然而比较特殊的是,这道题不能用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完就可以直接询问了。

怎么求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名上线。

奈特公司纪律森严,每名情报员只能与自己的上、下线联系,同时,情报网络中仟意两名情报员一定能够通过情报网络传递情报。

奈特公司每天会派发以下两种任务中的一个任务:

  1. 搜集情报:指派x号情报员搜集情报;
  2. 传递情报:将一条情报从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是大神。

原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/12075199.html

时间: 2024-11-10 14:20:35

「专题总结」LCT 2的相关文章

「专题总结」回文自动机PAM

为了备课,把做完的专题的总结咕了这么久... 主要是自己做题做的太慢了,所以讲SAM的时候准备也不充分. 在把讲课时间不断咕之后依然是粗制滥造,锅很多,所以效果很差.而且还有人没听懂... 一半人都做了5道题以上了,另一半人还没怎么看,基本所有人都有预习. 得不到任何反馈,也不知道速度如何.就当凑活吧. 挺失败的.可能也没有下一次机会了. 我也不知道后缀数组推荐率是怎么达到100%的...那次我讲的自己也很满意 一到难的知识点我就不行了嘛...主要是自己理解也很不深刻 至少也还是在3个多小时之内

「专题总结」线性基

为什么要把毫无关联的线性基和群论放在一个专题里呢..? 因为它们都很毒瘤 线性基本身还是比较简单的,用于处理一些数子集异或和的集合有哪些. 然而它的考察方法总是很神奇... 题目难度大致升序,但是没有前置知识关系: 元素: $Description:$ 相传,在远古时期,位于西方大陆的 Magic Land 上,人们已经掌握了用魔法矿石炼制法杖的技术.那时人们就认识到,一个法杖的法力取决于使用的矿石. 一般地,矿石越多则法力越强,但物极必反:有时,人们为了获取更强的法力而使用了很多矿石,却在炼制

「专题总结」半平面交(6/8completed)

还是题都没做完就来写一部分题解了,因为最近很忙(其实是效率太低),所以可能一时半会回不来这个专题. 为了防止什么都没剩下忘干净,于是乎瞎写个题解记录一下... 还是一如既往的不擅长集合. 刚开始我看到半平面交我还以为是用来解决三维计算几何问题的,吓一跳. 半平面不用多说,就是一条直线把一个二维平面分成两半,其中的一半. 半平面交其实很好理解,就是同一个二维平面中若干个半平面相交的部分. 维护的方法其实挺草率的,和凸包有不少互通的地方,只不过因为封闭所以是双端队列. 凸包偏向于点,半平面交偏向于线

「专题训练」Collecting Bugs(POJ-2096)

题意与分析 题意大致是这样的:给定一个\(n\times s\)的矩阵,每次可以随机的在这个矩阵内给一个格子染色(染过色的仍然可能被选中),问每一行和每一列都有格子被染色的次数的期望. 这题如果从概率(从正方向推)就会白给,不信你自己试试:而定义反方向的推导(\(e_{i,j}\)意为从i行j列已有染色格子到最后全被染色的次数的期望)就会非常简单:分四种情况讨论即可. 代码 #include <bits/stdc++.h> using namespace std; double e[1005]

怎样将「插件化」接入到项目之中?

本期移动开发精英社群讨论的主题是「插件化」,上网查了一下,发现一篇 CSDN 博主写的文章<Android 使用动态载入框架DL进行插件化开发>.此处引用原作者的话: 随着应用的不断迭代,应用的体积不断增大,项目越来越臃肿,冗余添加.项目新功能的加入,无法确定与用户匹配性,发生严重异常往往牵一发而动全身,仅仅能紧急公布补丁版本号,强制用户进行更新.结果频繁的更新.反而easy减少用户使用黏性,或者是公司业务的不断发展,同系的应用越来越多,传统方式须要通过用户量最大的主项目进行引导下载并安装.

「C语言」常量和变量的表示及应用

先发布,还在修改完善中.. 在程序运行中,其值不能改变的量成为常量.在基本数据类型中,常量可分为整型常量.实型常量.符号常量和字符型常量(包括字符常量和字符串常量),现分别介绍如下: 整型常量 即整常数,由一个或多个数字组成,可以带正负号 C语言中整型常量可用十进制.八进制和十六进制3种形式表示 十进制整数:由0~9数字组成,不能以0开始,没有前缀 八进制整数:以0为前缀,其后由0~7的数字组成,没有小数部分 十六进制整数:以0x或0X开头,其后由0~9的数字和a~f(或A~F字母组成) 另外长

Chrome 扩展 Stylish :给不喜欢某个网站一键「换肤」

原文地址:http://whosmall.com/?post=419 本文标签: Chrome扩展 Chrome浏览器 Chrome插件 Chrome扩展Stylish Stylish是什么 Stylish 是什么? 开门见山,Stylish 的作用是,它可以把百度首页变成这样: 它还能把知乎「拍扁」,让微博网页版变得简洁无比,让 Feedly 用上Material Design-- 这个神奇的 Stylish实际上是一个浏览器插件,适用于 Chrome,Firefox,Opera 以及 Saf

3D高科技投影 麦可「复活登台」幕后

美国告示牌音乐颁奖典礼,日前在赌城盛大举行,主办单位利用高科技投影技术,让麦可杰克森「复活」登台表演,3D全像投影,加上影片与真人舞群无缝接轨,高科技让过世的大明星彷佛活了过来. 流行乐天王麦可杰克森死而复生,过世将近5年的他,又现身在今年美国告示牌音乐颁奖典礼上,金光闪闪现身舞台中央,麦可杰克森回来了,再现招牌的动感舞步,流露巨星风采,主办单位利用3D全像摄影技术,秘密制作了半年多,把他带回到世人眼前. 特效专家:「观众在告示牌典礼上看到的是,麦可的头部数字影像,连接到一名演员身上,我们实时捕

「Maven Tips」(一)自动更新jar包

maven中手动去更新jar包,是一件比较繁琐麻烦的事情,使用Range Dependency则可以省去这一步骤. 部分jar包可能会自动升级到beat版本! 官方说明文档:Dependency Version Ranges Range Meaning (,1.0] version ≤ 1.0 1.0 固定1.0版本 [1.0]   [1.2,1.3] 1.2 ≤ version ≤ 1.3 [1.0,2.0) 1.0 ≤ version ≤ 2.0 [1.5,) version ≥ 1.5 (