1. 程式人生 > >【洛谷】NOIP2018原創模擬賽DAY2題解

【洛谷】NOIP2018原創模擬賽DAY2題解

前言:

我相信大家可以感覺到DAY2題目的難度明顯比DAY1大很多,這也是近年NOIP考試的趨勢,從目前NOIP考察的知識來看這次的T3知識可能對NOIP選手來說略難,但說不定今年NOIP還會考更高階的演算法,所以要有所防備。

再說一點,大家一定要注意部分分的獲取。對於這套題,如果能力一般的同學採用部分分演算法,理論至少上可以得到:100(預處理+動態規劃)+40(只解決純串聯或並聯情況)+55(採用暴力的“逐步爬山法”計算)=195分,對於DAY2來說是一個不錯的分數。對於T2,個人覺得細節相當多,而且不知道“標程”是否正確。對於T3,得部分分難度不大,但它就是一個防NOIP選手AK的題,可以說,對大部分人(包括我)來說這道題寫正解(200+ 行程式碼)還不如暴力(約100行程式碼)來得快。

最後,不要認為出題人能力比其他人強。因為出題沒有比賽那麼短的時間限制,所以可以花更多時間來解決自己的題。比如對於T2的第一個和第二個版本我自己都做不來,於是改為了現在的第三個版本,而且“標程”寫錯兩次;對於T3我自己寫了大約3個小時才寫完正解。所以這次比賽200+的同學都是能力在我之上的。                               ——於2018/10/14

當然題目方法不唯一,如果大家對DAY2的題目有更好的解法,歡迎提出!

T1:最後的戰役

考察知識:map,動態規劃

演算法難度:XXX+ 實現難度:XXX

說實話,我動態規劃很弱,所以就出了一道不那麼難的動態規劃。

這道題貪心並不是完美解法,但是資料為隨機生成的,所以得分概率非常大。

Hack資料:

4 2
1 1
2 3
3 6
4 8
1 2 3 4

標程輸出:22 貪心輸出:21

分析:

首先,我們要解決操作2,如果暴力列舉時間複雜度為O(n^2)

其實吧,我們直接用map優化就可以了,時間複雜度:O(nlogn)

    for(int i=1;i<=n;i++) P[i]=max(P[i-1],p[i]);//求最大值
    for(int i=1;i<=n;i++){
    	mp[k[i]]+=p[i];//在這裡求和
        P[i]=max(mp[x[i]],P[i]);
    }

在處理了伏地魔在 [1,n] 每一步可以獲得的最大魔法能量之後(記為P [ i ],記\sum P[i] 為 SUM),我們就可以採取動態規劃了:

定義:f(i,j)表示在[ 1 , i ] 秒中在 i 秒使用了魔法,且有 j 個時間段使用了魔法,可以得到的最大能量值

邊界:f(i,1)=SUM-P[i]+P[i+1]

狀態轉移:f(i,j)=max\left \{ f(k,j-1)-P[i]+P[i+1] \right \}\,\,\,\, 0<k<i-1

狀態轉移方程的實現還是要稍微處理一下,直接實現會超時:

    for(int i=1;i<=n;i++)//處理邊界
        f[i][1]=SUM-P[i]+P[i+1],
        ans=max(ans,f[i][1]);
    for(int j=2;j<=m;j++){
        for(int k=1;k<=n;k++) T[k]=max(T[k-1],f[k][j-1]);//預處理
        for(int i=2*j-1;i<=n;i++)
            f[i][j]=T[i-2]-P[i]+P[i+1],
            ans=max(ans,f[i][j]);
    }

好了,這道題我們就做出來了,理論時間複雜度:O(nlogn+nm),但是時間效率不是很高,面對最大資料需要近900ms。

T2:流量計算

考察知識:圖論,數學,推導,搜尋

演算法難度:XXXX 實現難度:XXXX

出題背景:我目前在學高中電學。最開始這道題搞得很難,我自己都做不來,於是不斷降低難度:從混連->並聯與並聯和串聯巢狀->並聯只能巢狀串聯。

分析:

這道題需要大家有一定的電學知識和數學推導能力。

先推導幾個電學結論:

結論一:對於串聯電路,我們可以將所有電阻R_1,R_2,...,R_n看作一個等效電阻:R_{\sum}=\sum_{i=1}^n R_i

證明:易得,略

結論二:對於一個(等效)並聯電路,電阻分別為R_1,R_2,...,R_n等效電阻為:R_{\sum}=\frac{1}{\sum_{i=1}^n \frac{1}{R_i}}

證明:設電壓為 U,由歐姆定律,總電流為:I_{\sum}=\sum_{i=1}^n \frac{U}{R_i},故等效電阻為:R_{\sum}=\frac{U}{I_{\sum}}=\frac{1}{\sum_{i=1}^n \frac{1}{R_i}}

結論三:對於一個(等效)並聯電路,電阻分別為R_1,R_2,...,R_n,我們可以採取下面的演算法計算等效電阻:

double R_=R[1];
for(int i=2;i<=n;i++) R_=R_*R[i]/(R_+R[i]);
ans=R_;

證明:結論二的二維形式:R_{1,2}=\frac{R_1R_2}{R_1+R_2},每次將兩個電阻合併為一個等效電阻,最後的值即為總等效電阻

結論四:對於一個(等效)並聯電路,電阻分別為R_1,R_2,...,R_n,其支路最小電流為:\frac{R_{\sum}I_{\sum}}{max\{R_i\}}

證明:我們知道,最小電流在電阻最大的支路上,即max\{R_i\},而電壓為R_{\sum}I_{\sum},由I=\frac{U}{R},就得到結論

有了這幾個結論,我們就可以開始動手了:

1.先用結論三處理重邊

2.然後bfs求出由電源正極到負極的一條路徑

3.以這條路徑為主線開始處理,遇到分叉邊就表明此處有並聯巢狀串聯,用結論一+dfs計算巢狀串聯的等效電阻,並記下k=min\{k,\frac{R_{\sum}}{max\{R_i\}}\},然後利用結論三進行並聯電阻的合併

4.將所有等效電阻求和,即為整個電路的總等效電阻,如果主線電流為 I 則最小電流 I_{min}=kI

程式碼實現細節請參考程式碼

T3:PION字尾自動機

考察知識:樹鏈剖分,線段樹,連結串列,排序,二分,字串處理

演算法難度:XXXXX 實現難度:XXXXX

分析:難題,演算法複雜,程式碼量大!這是一道典型的考場上正解不如寫暴力的題!

先介紹暴力演算法(55分):

演算法難度:XXX 實現難度:XXX+

顯然儲存檔案字尾資訊不能用陣列,空間開不下,所以我們用連結串列儲存所有資料夾中檔案的字尾資訊;

然後是字尾的處理,注意到字串長度小於6,如果我們用 1 表示 a ,2 表示 b ,... ... ,26 表示 z,這就相當於將字串看做一個27進位制數,這樣我們就可以在 int 範圍內用數字表示字串了

解決了上面兩個問題之後,我們就可以建樹然後對每個操作用暴力的 “逐步爬山法” 解決了,實現難度不大

其實這種方法對隨機資料效果還是比較好的(甚至比下面的滿分演算法還略快一點),但是你覺得資料可能完全隨機麼?

時間複雜度:極端情況約 O(mn)

滿分演算法:

程式碼量比較大。

看到這道題我們應該可以想到用樹鏈剖分來做。

我們先考慮怎麼對序列進行統計和修改,對於操作2,我們需要找到一個序列中的子序列。我們可以用一個結構體來儲存,結構體儲存序列中每個元素的值(字串的hash值)和序列中每個元素在序列中的位置。然後將這個結構體序列按元素值的排序(元素值相同的按在序列中的位置排序)。當我們需要查詢一個序列相同數值所在的區間的時候用 lower_bound 和 upper_bound 就可以完成,對於查找出來的目標區間,還需要用二分法來查詢該區間中元素的下標在目標子序列中的數量。

至於修改,用線段樹的區間維護實現。

解決了對序列的修改,我們還要解決樹中每個節點含有的檔案數目不相同的問題,如果一個節點沒有檔案,那麼簡單的方法是新建一個偽檔案,可以將hash值賦為 -1。

之後就是樹鏈剖分了,不同於樹鏈剖分模板,我們還要新建一個輔助陣列 low[i] ,表示節點 i 的重兒子的重兒子...(以此類推)可以到達的最深節點的編號,我們先對[\,top[i],low[i]\,]排序,之後查詢的時候我們查詢 [\,top[i],low[i]\,] 之間的序列即可。

其實吧,上面只是程式碼實現的一部分,具體細節遠不止這些,更多的細節請參考程式碼。

時間複雜度:極端情況約 O((n+m)log^2n)

程式碼:

(僅供參考)

T1:

#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
using namespace std;
const int maxn=50005;
map<int,int>mp;
int k[maxn],p[maxn],x[maxn];
int n,m,P[maxn],f[maxn][505],T[maxn],sum[maxn],SUM;
void ready(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d%d",k+i,p+i);
    for(int i=1;i<=n;i++) scanf("%d",x+i);
    for(int i=1;i<=n;i++) P[i]=max(P[i-1],p[i]);
    for(int i=1;i<=n;i++){
    	mp[k[i]]+=p[i];//用map儲存即可 
        P[i]=max(mp[x[i]],P[i]);
    }
}
void dp(){
/*
f(i,j)表示已i為結尾且有j個時間段使用了魔法,可以得到的最大能量值
f(i,j)=MAX{f(k,j-1)}-P[i]+P[i+1]  1<j<=i-2 
*/ 
    int ans;
    for(int i=1;i<=n;i++) SUM+=P[i];
    ans=SUM;
    if(m==0) {printf("%d\n",ans);return;}
    for(int i=1;i<=n;i++)
        f[i][1]=SUM-P[i]+P[i+1],
        ans=max(ans,f[i][1]);
    for(int j=2;j<=m;j++){
        for(int k=1;k<=n;k++) T[k]=max(T[k-1],f[k][j-1]);//類似於預處理 
        for(int i=2*j-1;i<=n;i++)
            f[i][j]=T[i-2]-P[i]+P[i+1],
            ans=max(ans,f[i][j]);
    }
    printf("%d\n",ans);
}
int main(){
    ready();
    dp();
    return 0;
}

T2:

#include<map>
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=20005;
map<pair<int,int>,double>mp,mp2;
map<pair<int,int>,double>::iterator it;
struct edge{
	int to,next;
	double R;
}e[maxn*5];
int head[maxn],np;
void adde(int u,int v,double R){
	e[++np]=(edge){v,head[u],R};
	head[u]=np;
	e[++np]=(edge){u,head[v],R};
	head[v]=np;
}
int n,m,from,to;
bool vis[maxn],done1[maxn],done2[maxn];
int d[maxn],fa[maxn],son[maxn];
double U,k=1.0,R_sum,R_fa[maxn],T[maxn];
void build(){
	int R,u,v;
	char S[5];
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d%s%d",&u,&v,S,&R);
		if(S[0]=='P') from=u,to=v,U=(double)R;//找到電源 
		else{
			if(u>v) swap(u,v);
			pair<int,int>pr=make_pair(u,v);
			if(mp.count(pr)){//處理重邊構成的並聯
				double R_=mp[pr];
				mp[pr]=R_*R/(R+R_);//並聯電路電阻的計算 
				mp2[pr]=max(mp2[pr],(double)R);
			}
			else mp[pr]=mp2[pr]=(double)R;
		}
	}
	for(it=mp.begin();it!=mp.end();it++){
		pair<int,int>pr=it->first;
		k=min(k,mp[pr]/mp2[pr]);//k的計算 
		adde(pr.first,pr.second,it->second);
	}
}
void bfs(int s){//尋找路徑 
	memset(d,0x3f,sizeof(d));
	memset(vis,0,sizeof(vis));
	fa[s]=d[s]=0,vis[s]=true;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int i=q.front();q.pop();
		for(int p=head[i];p;p=e[p].next){
			int j=e[p].to;
			if(vis[j]) continue;
			fa[j]=i,R_fa[j]=e[p].R;
			vis[j]=true,d[j]=d[i]+1;
			q.push(j);
		}
	}
}
int dfs(int i,double& sum){//尋找支路 
	for(int p=head[i];p;p=e[p].next){
		int j=e[p].to;
		if(done2[j]) continue;
		sum+=e[p].R;
		if(done1[j]) return j;//找到另一個交匯點 
		else {done2[j]=true;return dfs(j,sum);}
	} return -1;
}
void solve(){
	bfs(from);
	for(int i=to;i;i=fa[i]) done1[i]=true,son[fa[i]]=i;//標記主線 
	for(int i=to;i!=from;i=fa[i]){
		double R_max,R_=0;//R_:等效電阻 
		int cnt=0,pos;
		for(int p=head[i];p;p=e[p].next){
			int j=e[p].to;
			if(done1[j]||done2[j]) continue;
			T[++cnt]=mp[make_pair(min(i,j),max(j,i))];
			done2[i]=done2[j]=true;//標記支線 
			pos=dfs(j,T[cnt]);
		}
		if(!cnt){R_sum+=R_fa[i];continue;}//當前路線不包含並聯
		for(int j=i;j!=pos;j=fa[j]) R_+=R_fa[j];
		R_max=R_;
		for(int j=1;j<=cnt;j++){
			R_max=max(R_max,T[j]);
			R_=R_*T[j]/(R_+T[j]);
		}
		k=min(k,R_/R_max);
		R_sum+=R_,i=son[pos];
	}
	printf("%.2lf\n%.2lf\n",U/R_sum,U*k/R_sum);
    printf("%d",1/0);
}
int main(){
	build();
	solve();
	return 0;
}

T3:(暴力演算法)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100005;
int next[maxn*5],hash[maxn*5],first[maxn],last[maxn],np_;
void add_file(int u,int num){//連結串列實現按位置儲存檔案 
	hash[++np_]=num;
	if(first[u]) next[last[u]]=np_;
	else first[u]=np_;
	last[u]=np_;
}
int hash_num(char* s){
	int ret=0;
	for(int i=0;s[i]!=0;i++) ret=ret*27+s[i]-96;
	return ret;
}
struct edge{//儲存樹邊 
    int to,next;
}e[maxn*2];
int head[maxn],np;
void adde(int u,int v){//新增樹邊 
    e[++np]=(edge){v,head[u]};
    head[u]=np;
    e[++np]=(edge){u,head[v]};
    head[v]=np;
}
int n,m,k[maxn],dep[maxn],fa[maxn];
void dfs(int i,int Fa){
	fa[i]=Fa,dep[i]=dep[Fa]+1;
	for(int p=head[i];p;p=e[p].next){
		int j=e[p].to;
		if(j==Fa) continue;
		dfs(j,i);
	}
}
void build(){
	int u,v;
	char ext_nm[10];
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++) scanf("%d%d",&u,&v),adde(u,v);
	for(int i=1;i<=n;i++){
		scanf("%d",k+i);
		for(int j=1;j<=k[i];j++){
			scanf("%s",ext_nm);
			add_file(i,hash_num(ext_nm));//插入連結串列 
		}
	}
	dfs(1,0);
}
int lca(int x,int y){
	while(x!=y){
		if(dep[x]<dep[y]) y=fa[y];
		else x=fa[x];
	}
	return x;
}
int query_path(int x,int y,int id,bool del){//暴力處理資料夾 
	int ret=0;
	while(x!=y){
		if(dep[x]<dep[y]) swap(x,y);
		for(int p=first[x];p;p=next[p]) if(hash[p]==id){
			ret++;
			if(del) hash[p]=-1;
		}
		x=fa[x];
	}
	for(int p=first[x];p;p=next[p])if(hash[p]==id){
		ret++;
		if(del) hash[p]=-1;
	}
	return ret;
}
void solve(){
	char cmd[3][10];
	int u,v,id;
	while(m--){
		scanf("%s%s",cmd[0],cmd[1]);
		if(cmd[0][0]=='q'){
			if(cmd[1][1]=='p'){
				scanf("%d%d",&u,&v);
				printf("%d\n",dep[u]+dep[v]-2*dep[lca(u,v)]);
			} else {
				scanf("%d%d%s",&u,&v,cmd[2]);
				id=hash_num(cmd[2]+2);
				printf("%d\n",query_path(u,v,id,false));
			}
		}
		else{
			scanf("%d%d%s",&u,&v,cmd[2]);
			id=hash_num(cmd[2]+2);
			printf("%d\n",query_path(u,v,id,true));
		}
	}
    printf("%d",1/0);//Don't copy my code !
}
int main(){
    build();
    solve();
    return 0;
}

*T3:(正解)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100005;
int next[maxn*5],hash[maxn*5],first[maxn],last[maxn],np_;
void add_file(int u,int num){//連結串列實現按位置儲存檔案 
	hash[++np_]=num;
	if(first[u]) next[last[u]]=np_;
	else first[u]=np_;
	last[u]=np_;
}
int hash_num(char* s){ 
	int ret=0;
	for(int i=0;s[i]!=0;i++) ret=ret*27+s[i]-96;
	return ret;
}
struct edge{//儲存樹邊 
    int to,next;
}e[maxn*2];
int head[maxn],np;
void adde(int u,int v){//新增樹邊 
    e[++np]=(edge){v,head[u]};
    head[u]=np;
    e[++np]=(edge){u,head[v]};
    head[v]=np;
}
struct ext_name{
    int v,id;
    const bool operator < (const ext_name& B)const {
        return v<B.v;
    }
}D[maxn*6];
bool cmp(const ext_name& A,const ext_name& B){
    return A.v<B.v||(A.v==B.v&&A.id<B.id);
}
int n,m,k[maxn];
/*--------------------線段樹----------------------*/
int rt,nowpos,lc[maxn*12],rc[maxn*12],sum[maxn*12],setv[maxn*12];
#define pushup(now) sum[now]=sum[lc[now]]+sum[rc[now]]
void pushdown(int now,int l,int r,int M){
	if(setv[now]!=-1) return;
	sum[lc[now]]=sum[rc[now]]=0;
	setv[lc[now]]=setv[rc[now]]=-1;
	setv[now]=0;
}
void build(int& now,int l,int r){
	now=++nowpos;
	if(l==r) {sum[now]=1;return;}
	int M=(l+r)>>1;
	build(lc[now],l,M);
	build(rc[now],M+1,r);
	pushup(now);
}
void update(int now,int l,int r,int x,int y){
	if(x<=l&&r<=y) {setv[now]=-1,sum[now]=0;return;}
	int M=(l+r)>>1;
	pushdown(now,l,r,M);
	if(y<=M) update(lc[now],l,M,x,y);
	else if(x>M) update(rc[now],M+1,r,x,y);
	else update(lc[now],l,M,x,y),update(rc[now],M+1,r,x,y);
	pushup(now);
}
int query(int now,int l,int r,int x,int y){
	if(x<=l&&r<=y) return sum[now];
	int M=(l+r)>>1;
	pushdown(now,l,r,M);
	if(y<=M) return query(lc[now],l,M,x,y);
	else if(x>M) return query(rc[now],M+1,r,x,y);
	else return query(lc[now],l,M,x,y)+query(rc[now],M+1,r,x,y);
}
/*-------------------樹鏈剖分--------------------*/
int fa[maxn],son[maxn],dep[maxn],sz[maxn];
int P,top[maxn],seg[maxn],rev[maxn],low[maxn];
int lP[maxn],tP[maxn],pos;
void dfs1(int i,int Fa){
	dep[i]=dep[Fa]+1,fa[i]=Fa,sz[i]=1;
	for(int p=head[i];p;p=e[p].next){
		int j=e[p].to;
		if(j==Fa) continue;
		dfs1(j,i);
		sz[i]+=sz[j];
		if(sz[son[i]]<sz[j]) son[i]=j;
	}
}
void load_files(int segl,int segr){
	sort(D+lP[segl],D+tP[segr]+1,cmp);
}
void init_folder(int u){
	int i=rev[u];
	lP[u]=pos+1;
	if(k[i]){
		for(int p=first[i];p;p=next[p])
			D[++pos].v=hash[p],D[pos].id=u;
		tP[u]=pos;
	}
	else D[++pos].v=-1,D[pos].id=u,tP[u]=pos;//如果為空資料夾裝載假檔案 
}
void dfs2(int i){
	if(sz[i]==1)
		load_files(seg[top[i]],seg[i]),low[i]=i;//將檔案排序 
	if(son[i]){
		top[son[i]]=top[i];
		seg[son[i]]=++P,rev[P]=son[i];
		init_folder(P);//將資料夾中的檔案寫入序列 
		dfs2(son[i]);
		low[i]=low[son[i]];
	}
	for(int p=head[i];p;p=e[p].next){
		int j=e[p].to;
		if(top[j]) continue;
		top[j]=j,seg[j]=++P,rev[P]=j;
		init_folder(P);
		dfs2(j);
	}
}
int lca(int x,int y){
	int fx=top[x],fy=top[y];
	while(fx!=fy){
		if(dep[fx]<dep[fy]) swap(fx,fy),swap(x,y);
		x=fa[fx],fx=top[x];
	}
	if(dep[x]<dep[y]) return x;
	return y;
}
int calc(int l,int r,int L__,int R__,int id,bool del){//最難懂的部分之一,用二分尋找子序列位置
	ext_name Tmp;Tmp.v=id;
	L__=lP[L__],R__=tP[R__];
	int L_=lower_bound(D+L__,D+R__+1,Tmp)-D,L;
	int R_=upper_bound(D+L__,D+R__+1,Tmp)-D,R;
	if(L_>=R_) return 0;//沒有找到
	R_--;
	int l_=R_+1,r_=L_-1;
	L=L_,R=R_;
	while(L<=R){//二分尋找
		int M=(L+R)>>1;
		if(D[M].id>=l) l_=M,R=M-1;
		else L=M+1;
	}
	L=L_,R=R_;
	while(L<=R){
		int M=(L+R)>>1;
		if(D[M].id<=r) r_=M,L=M+1;
		else R=M-1;
	}
	if(l_>r_) return 0;
	int TMP=query(rt,1,pos,l_,r_);//線上段樹中查詢 
	if(del) update(rt,1,pos,l_,r_);//刪除檔案 
	return TMP;
}
int query_path(int x,int y,int id,bool Access_y){//樹鏈剖分中跳路徑的方法
	int fx=top[x],fy=top[y],ret=0;
	while(fx!=fy){
		ret+=calc(seg[fx],seg[x],seg[fx],seg[low[fx]],id,false);
		x=fa[fx],fx=top[x];
	}
	if(Access_y) ret+=calc(seg[y],seg[x],seg[top[y]],seg[low[y]],id,false);
	else ret+=calc(seg[y]+1,seg[x],seg[top[y]],seg[low[y]],id,false);
	return ret;
}
int del_path(int x,int y,int id){
	int fx=top[x],fy=top[y],ret=0;
	while(fx!=fy){
		ret+=calc(seg[fx],seg[x],seg[fx],seg[low[fx]],id,true);
		x=fa[fx],fx=top[x];
	}
	ret+=calc(seg[y],seg[x],seg[top[y]],seg[low[y]],id,true);
	return ret;
}
void build(){
	int u,v;
	char ext_nm[10];
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++) scanf("%d%d",&u,&v),adde(u,v);
	for(int i=1;i<=n;i++){
		scanf("%d",k+i);
		for(int j=1;j<=k[i];j++){
			scanf("%s",ext_nm);
			add_file(i,hash_num(ext_nm));//插入到連結串列 
		}
	}
	dfs1(1,0);
	seg[1]=top[1]=P=rev[1]=1;
	init_folder(1);
	dfs2(1);
	build(rt,1,pos);//初始化線段樹 
}
void solve(){
	char cmd[3][10];
	int u,v,a,id;
	while(m--){
		scanf("%s%s",cmd[0],cmd[1]);
		if(cmd[0][0]=='q'){
			if(cmd[1][1]=='p'){
				scanf("%d%d",&u,&v);
				printf("%d\n",dep[u]+dep[v]-2*dep[lca(u,v)]);
			}
			else{
				scanf("%d%d%s",&u,&v,cmd[2]);
				a=lca(u,v),id=hash_num(cmd[2]+2);
				printf("%d\n",query_path(u,a,id,true)+query_path(v,a,id,false));
			}
		}
		else{
			scanf("%d%d%s",&u,&v,cmd[2]);
			a=lca(u,v),id=hash_num(cmd[2]+2);
			printf("%d\n",del_path(u,a,id)+del_path(v,a,id));
		}
	}
    printf("%d",1/0);//Dont't copy my code !
}
int main(){
    build();
    solve();
    return 0;
}