差不多理解板子之后,寫了一些奇怪的題。
但是還是那個問題:樹剖真好使。
魔法森林: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是大神。