1. 程式人生 > 實用技巧 >5 7 級 錯 位 打 ♂ 擊 賽

5 7 級 錯 位 打 ♂ 擊 賽

5 7 級 錯 位 打 擊 賽

本篇部落格並不會講任何演算法,所以極水

博主寫這篇部落格完全是出於在機房頹廢沒有事情做,順便整理一下模擬考試的題目,所以不會像之前那樣完善

T1

傳送門:https://www.luogu.com.cn/problem/U121235?contestId=31441

顯然,這是一個數據結構題(線段樹),但是由於考試的時候沒有推出標記下傳的方法來,所以寫了一個暴力,拿了40分的部分分

因為本人寫的程式碼實在太糟糕了(其實是懶得寫,反正有學長的詳細思路解釋,我就直接\(copy\)啦),所以以下是學長的T1題解

線段樹板子題沒人 \(AC\) 嘛?

前言

出題人本來打算給你們 \(40pts\)

的暴力分,結果沒想到資料出水了,你們暴力竟然能拿 \(70pts\),給了你們一個 \(T1\) 很好做的樣子。

本著信心賽的原則,我還是很希望你們能多拿一點分數,給教練一個肯定的回答,讓大家的分數好看點。

還是很希望你們能認真地聽完 \(30pts\) 的正解分。

當然現在我加強了資料,暴力只能拿 \(40pts\) 了吧。

\(10pts\)

對於 \(10\)% 的資料,\(x=0\)

操作 \(1\) 再怎麼花裡胡哨都是沒用的,我們只需要考慮操作 \(2\) 即可。

區間求和問題,可以直接用字首和來做,時間複雜度 \(Θ(n)\)

期望得分 \(:10pts\)

\(20pts\)

對於另外 \(10\)

% 的資料,\(1<=l=r<=n\)

只是多了一個單點修改的操作,只需注意我們在修改的時候是 \(-x\),可以用線段樹或樹狀陣列來做,時間複雜度 \(Θ(nlog_n)\)

期望得分 \(:20pts\)

\(40pts\)

對於另外 \(20\)% 的資料,\(1<=n,m<=5000\)\(-100<=a_i,x<=100\)

考慮到 \(n\)\(m\) 的範圍很小,我們直接用最樸素的演算法 "暴力" 直接做即可,時間複雜度 \(Θ(nm)\)

\(100pts\)

對於 \(100\)%的資料,保證 \(1<=n,m<=3*10^5\)

\(-1000<=a_i,x<=1000\)\(1<=l,r<=n\)

看到這個資料範圍,正解肯定是 \(Θ(nlog_n)\) 的資料結構無疑了 。

由於出題人沒有什麼好的做法,那麼這道題我們還是用線段樹做吧。

思考一下這道題和你們做的線段樹板子有什麼不同,很顯然它的操作 \(1\) 變得更加毒瘤且噁心。

但是換湯不換藥,線段樹板子的套路在這道題上還是可以應用的,那就是:懶標記

回顧一下區間加、區間求和的懶標記:

if(x<=l&&r<=y)
{
	lazy[node]+=v;
	sum[node]+=(r-l+1)*v;
	return ;
}

比著葫蘆畫瓢,我們做這道題的時候也設定一個懶標記:

\(lazy1[node]=k\) 表示線段樹中 \(node\) 節點所代表的區間進行了一次操作 \(1\),要時刻注意操作 \(1\) 是以 \(-\) 開始的。

然後我們思考一下怎麼 \(Θ(1)\) 地對 \(sum[node]\) 進行維護:

找規律:我們可以將第 \(k\) 個和第 \(k+1\) 個看成一組,它們的貢獻之和為 \(x\) ,那麼所有組的貢獻總和為:\(x*\) 組數 :

但是可能還會存在一個落單的,別忘了計算這個落單的貢獻。

對於 \(lazy1[node]\) 的維護,我們直接令 \(lazy1[node]+=k\) 即可 。

這樣就可以了嘛?真 · 線段樹板子?上述做法有什麼問題嘛?

\(Problem1:\) 正負號問題 。

一個很重要的問題:我們上面的操作都是基於第一個數是 \(-\) 才行,也就是說,我們的操作形式要和題目中給出的操作 \(1\) 的形式一直才對 。

題目中的操作 \(1\) 不是以 \(-\) 的形式開始的嘛?為什麼當前區間第一個數可能會是 \(+\) 的形式?

如上圖所示:如果你要對 \([1,5]\) 進行操作 \(1\) ,當你遞迴到你的右兒子 \([4,5]\) 的時候發現是以 \(+\) 開頭的,但是由於左兒子的最左端就是它父親的最左端,所以左兒子開頭的正負性和父親開頭的正負性是相等的,我們比較好判斷,我們重點要判斷右兒子的開頭的正負性 。

想一想哈,如果左兒子的區間長度為 \(2k\),那麼右兒子開頭的正負性和父親的相同;如果左兒子的區間長度為 \(2k+1\),那麼右兒子開頭的正負性和父親的相反 。

\(Problem2:\) 標記下傳問題

注意一下我們 \(lazy1[node]=k\) 的定義:表示線段樹中 \(node\) 節點所代表的區間進行了一次操作 \(1\)

注意到操作 \(1\) 是有兩個性質的:

①. 正負號交替;②. 每一項的絕對值之差為首項的絕對值。

注意到上圖右兒子中,操作 \(1\) 的首項為 \(+4x\) ,而每一項的絕對值之差為 \(x\),不符合操作 \(1\) 的定義了,所以我們不能直接維護懶標記。

提取公因式:我們將 \(+4x\) 看作是 \(+3x+x\),將 \(-5x\) 看作是 \(-3x-2x\),注意到右邊的 \(+x\)\(-2x\) 了沒,這不就是操作 \(1\) 嘛?對於左邊的 \(+3x\)\(-3x\) 呢,我們可以看出一種新的操作:

\(3.l,r,y\) 表示將 \([l,r]\) 的第 \(l\) 個數減 \(y\),第 \(l+1\) 個數加 \(y\),第 \(l+2\) 個數減 \(y\) ......

即:令 \(a_l=a_l-y\)\(a_{l+1}=a_{l+1}+y\)\(a_{l+2}=a_{l+2}-y\) ...... \(a_r=a_r+(-1)^{r-l+1}y\)

所以對於上面的右兒子,我們可以看作是先進行了一次 \(x=-x\) 的操作 \(1\) ,再加上一次 \(y=-y\) 的操作 \(3\)

對於操作 \(3\),我們同樣開一個懶標記 \(lazy2[node]=k\) 來進行維護,有 \(1\) 必有 \(2\)\(qwq\)

\(insert\)

void insert(int node,int l,int r,int x,int y,int k)
{
	if(x<=l&&r<=y)       //[l,r]被完全覆蓋 
	{
		int len=r-l+1;   //當前區間的長度 
		int d=l-x;       //x~l-1之間有多少個數 
		if(d&1)          //如果l前面有有奇數個數,說明第l個數在[x,y]內是第偶數個數,那麼應該是從加號開始 
		{
			lazy1[node]-=k;  //由於我們lazy1的設定是從減號開始的,所以這裡應該是-= 
			lazy2[node]-=k*d;
			   //將操作1進行拆解,所提取出來的公因數的絕對值為k*d,同樣我們操作3的設定也是從減號開始,所以這裡還是-= 
			
			sum[node]-=(len/2)*k;  //對sum[node]進行維護,將它們兩兩看成一組,一共有len/2組,每一組的貢獻為-k 
			if(len&1) sum[node]+=k*(r-x+1); 
			   //如果區間長度為奇數,說明兩兩一組有餘,由於我們已經判斷出該區間是從加號開始的,所以這個餘出的數一定是加 
		}
		else             //從減開始,與上面的同理,只是符號都改成相反的而已 
		{
			lazy1[node]+=k;
			lazy2[node]+=k*d;
			sum[node]+=(len/2)*k;
			if(len&1) sum[node]-=k*(r-x+1);
		}
		return ;
	}
	pushdown(node,l,r);  //記得標記下傳   
	int mid=(l+r)>>1;
	if(x<=mid) insert(node<<1,l,mid,x,y,k);
	if(y>mid) insert(node<<1|1,mid+1,r,x,y,k);
	update(node);        //維護父親節點的sum 
}

\(pushdown\)

如果你已經理解了 \(insert\) 的思路,那麼 \(pushdown\) 的思路也就不難了。

void pushdown(int node,int l,int r)
{
	int mid=(l+r)>>1;
	int len=r-l+1;
	int len1=mid-l+1;
	int len2=r-mid;
	if(lazy1[node])                           //時刻牢記lazy1是從減開始的 
	{
		sum[node<<1]+=(len1/2)*lazy1[node];   //兩兩分組算貢獻 
		if(len1&1) sum[node<<1]-=len1*lazy1[node];  //兩兩分組有餘,那麼餘的這個數肯定是減的 
		lazy1[node<<1]+=lazy1[node];          //維護左兒子的lazy1 
		if(len1&1)                            //如果左兒子的區間長度為奇數,說明右兒子的左端點是第偶數個數,則是從加開始 
		{
			lazy1[node<<1|1]-=lazy1[node];    //由於是從加開始,所以這裡是減 
			lazy2[node<<1|1]-=lazy1[node]*len1; 
			   //只有右兒子不滿足操作1的性質②,因此我們要拆分操作,所以我們只維護右兒子的lazy2 
			sum[node<<1|1]-=(len2/2)*lazy1[node]; //兩兩分組算貢獻 
			if(len2&1) sum[node<<1|1]+=lazy1[node]*len; //兩兩分組有餘,由於右兒子是從加開始的,所以餘的這個數肯定是加的 
		} 
		else                                  //右兒子的左端點從減開始,除了符號和上面都一樣 
		{
			lazy1[node<<1|1]+=lazy1[node];
			lazy2[node<<1|1]+=lazy1[node]*len1;
			sum[node<<1|1]+=(len2/2)*lazy1[node];
			if(len2&1) sum[node<<1|1]-=lazy1[node]*len;
		}
		lazy1[node]=0;                        //別忘了清空標記1 
	}
	if(lazy2[node])                           //下傳標記2 
	{
		if(len1&1) sum[node<<1]-=lazy2[node]; //對於操作3,發現兩兩算貢獻的時候可以抵消,所以如果有餘肯定是減的 
		lazy2[node<<1]+=lazy2[node];          //維護左兒子的lazy2 
		if(len1&1)                            //右兒子的左端點從加開始    
		{
			lazy2[node<<1|1]-=lazy2[node];    //維護右兒子的lazy2,由於右兒子的左端點是從加開始的,所以這裡是減 
			if(len2&1) sum[node<<1|1]+=lazy2[node];
		} 
		else                                  //右兒子的左端點從減開始,仍然只有符號不同 
		{
			lazy2[node<<1|1]+=lazy2[node];
			if(len2&1) sum[node<<1|1]-=lazy2[node];
		}
		lazy2[node]=0;                        //清空lazy2 
	}
}

完整版\(Code:\)

#include<iostream>
#include<cstdio>
using namespace std;
int read()
{
	char ch=getchar();
	int a=0,x=1;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') x=-x;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		a=(a<<1)+(a<<3)+(ch-'0');
		ch=getchar();
	}
	return a*x;
}
const int N=1e5+5;
int n,m;
int a[N],sum[N<<2],lazy1[N<<2],lazy2[N<<2];
void update(int node)
{
	sum[node]=sum[node<<1]+sum[node<<1|1];
}
void pushdown(int node,int l,int r)
{
	int mid=(l+r)>>1;
	int len=r-l+1;
	int len1=mid-l+1;
	int len2=r-mid;
	if(lazy1[node])                           //時刻牢記lazy1是從減開始的 
	{
		sum[node<<1]+=(len1/2)*lazy1[node];   //兩兩分組算貢獻 
		if(len1&1) sum[node<<1]-=len1*lazy1[node];  //兩兩分組有餘,那麼餘的這個數肯定是減的 
		lazy1[node<<1]+=lazy1[node];          //維護左兒子的lazy1 
		if(len1&1)                            //如果左兒子的區間長度為奇數,說明右兒子的左端點是第偶數個數,則是從加開始 
		{
			lazy1[node<<1|1]-=lazy1[node];    //由於是從加開始,所以這裡是減 
			lazy2[node<<1|1]-=lazy1[node]*len1; 
			   //只有右兒子不滿足操作1的性質②,因此我們要拆分操作,所以我們只維護右兒子的lazy2 
			sum[node<<1|1]-=(len2/2)*lazy1[node]; //兩兩分組算貢獻 
			if(len2&1) sum[node<<1|1]+=lazy1[node]*len; //兩兩分組有餘,由於右兒子是從加開始的,所以餘的這個數肯定是加的 
		} 
		else                                  //右兒子的左端點從減開始,除了符號和上面都一樣 
		{
			lazy1[node<<1|1]+=lazy1[node];
			lazy2[node<<1|1]+=lazy1[node]*len1;
			sum[node<<1|1]+=(len2/2)*lazy1[node];
			if(len2&1) sum[node<<1|1]-=lazy1[node]*len;
		}
		lazy1[node]=0;                        //別忘了清空標記1 
	}
	if(lazy2[node])                           //下傳標記2 
	{
		if(len1&1) sum[node<<1]-=lazy2[node]; //對於操作3,發現兩兩算貢獻的時候可以抵消,所以如果有餘肯定是減的 
		lazy2[node<<1]+=lazy2[node];          //維護左兒子的lazy2 
		if(len1&1)                            //右兒子的左端點從加開始    
		{
			lazy2[node<<1|1]-=lazy2[node];    //維護右兒子的lazy2,由於右兒子的左端點是從加開始的,所以這裡是減 
			if(len2&1) sum[node<<1|1]+=lazy2[node];
		} 
		else                                  //右兒子的左端點從減開始,仍然只有符號不同 
		{
			lazy2[node<<1|1]+=lazy2[node];
			if(len2&1) sum[node<<1|1]-=lazy2[node];
		}
		lazy2[node]=0;                        //清空lazy2 
	}
}
void build(int node,int l,int r)
{
	if(l==r) 
	{
		sum[node]=a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(node<<1,l,mid);
	build(node<<1|1,mid+1,r);
	update(node);
}
void insert(int node,int l,int r,int x,int y,int k)
{
	if(x<=l&&r<=y)       //[l,r]被完全覆蓋 
	{
		int len=r-l+1;   //當前區間的長度 
		int d=l-x;       //x~l-1之間有多少個數 
		if(d&1)          //如果l前面有有奇數個數,說明第l個數在[x,y]內是第偶數個數,那麼應該是從加號開始 
		{
			lazy1[node]-=k;  //由於我們lazy1的設定是從減號開始的,所以這裡應該是-= 
			lazy2[node]-=k*d;
			   //將操作1進行拆解,所提取出來的公因數的絕對值為k*d,同樣我們操作3的設定也是從減號開始,所以這裡還是-= 
			sum[node]-=(len/2)*k;  //對sum[node]進行維護,將它們兩兩看成一組,一共有len/2組,每一組的貢獻為-k 
			if(len&1) sum[node]+=k*(r-x+1); 
			   //如果區間長度為奇數,說明兩兩一組有餘,由於我們已經判斷出該區間是從加號開始的,所以這個餘出的數一定是加 
		}
		else             //從減開始,與上面的同理,只是符號都改成相反的而已 
		{
			lazy1[node]+=k;
			lazy2[node]+=k*d;
			sum[node]+=(len/2)*k;
			if(len&1) sum[node]-=k*(r-x+1);
		}
		return ;
	}
	pushdown(node,l,r);  //記得標記下傳   
	int mid=(l+r)>>1;
	if(x<=mid) insert(node<<1,l,mid,x,y,k);
	if(y>mid) insert(node<<1|1,mid+1,r,x,y,k);
	update(node);        //維護父親節點的sum 
}
int query(int node,int l,int r,int x,int y)
{
	if(x<=l&&r<=y) return sum[node];
	pushdown(node,l,r);
	int mid=(l+r)>>1;
	int cnt=0;
	if(x<=mid) cnt+=query(node<<1,l,mid,x,y);
	if(y>mid) cnt+=query(node<<1|1,mid+1,r,x,y);
	return cnt; 
}
int main()
{
	//freopen("country.in","r",stdin);
	//freopen("country.out","w",stdout); 
	n=read();m=read();
	for(int i=1;i<=n;i++)
	    a[i]=read();
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		int opt,l,r,x;
		opt=read();
		l=read();r=read();
		if(l>r) swap(l,r);
		if(opt==1) 
		{
			x=read();
			insert(1,1,n,l,r,x);
		}
		else printf("%d\n",query(1,1,n,l,r));
	}
	return 0;
} 

\(The\) \(last\)

線段樹是一種很優秀的資料結構,希望你們能多加練習,體會它的魅力。

本著出於讓你們練一練線段樹的目的,我才出的這道題,沒想到正解還真的不好做。出題人埋頭苦思想了 \(2.5h\) 才想出正解,當然可能有更優的方法等待著你們來挖掘。

最後膜一下你們這群 \(Θ(n^2)\)\(1e4\) 資料的卡常帶師。

​ ——暗ざ之殤

T2

講道理,憑良心說話,T2要比T1簡單

傳送門:https://www.luogu.com.cn/problem/U121369?contestId=31441

\(30pts\)

**首先感謝學長不殺之恩,看到資料範圍,瞭解到一個\(30pts\)的做法,因為\(n=1\),所以輸出\(0\)即可

\(100pts\)

題目描述的已經很清楚了,把問題抽象化出來,就是求節點\(1\)的單源最短路,再求所有節點到節點\(1\)的單源最短路,累加答案即可

首先,我們看到,求所有到\(1\),求\(1\)到所有第一個想到的解法應該是\(floyd\)

這個演算法可以在\(O(n^3)\)的時間內求出全源最短路,然後我們呼叫我們需要的資料,累加答案即可

但是顯然這不是正解,因為:

對於\(100\)%的資料,有\(n<=1000\)

\(1000^3\)你玩呢?所以我又想出瞭如下方案:

1、跑\(n\)遍堆優化的\(dijkstra\),然鵝,複雜度\(O(n*(m+n)logn)\),並沒有什麼實質上的優化

2、\(n\)\(spfa\),最壞複雜度\(O(n^2*m)\),依然是我們無法接受的量級

暫時沒有思路,所以我在小本子上把樣例畫了出來,發現了這樣一個有趣的現象:

(原諒我是靈魂畫手)

我們發現,如上圖(假設邊權都是\(1\)),我們跑一遍\(1\)->\(4\)的最短路,是\(1\)->\(3\)->\(4\),如果我們把邊反著建,再跑一邊最短路,就是\(1\)->\(4\),也就是正向圖\(4\)->\(1\)的最短路

所以我直接亂搞一波反向建邊+兩遍\(dijkstra\)(反正是樂多賽制可以實時觀測評測結果)

直接交上去發現,真就切掉了。。。

\(AC\) \(Code\)(後來我發現洛谷上還有一個幾乎一模一樣的題P1629,傳送門:https://www.luogu.com.cn/problem/P1629

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
#define N 1010
using namespace std;
typedef long long int ll;
priority_queue< pair<int,int> >q;
ll mapp[N][N],dis[N],vis[N],cop[N],n,m,d,ans;
void upside_down(){
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
			swap(mapp[i][j],mapp[j][i]);//這裡是反向建邊(我比較懶直接用的鄰接矩陣
}
void dijkstra(){
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[1]=0;
	q.push(make_pair(0,1));
	while(q.size()!=0){	
		int x=q.top().second;
		q.pop();
		vis[x]=1;
		for(int y=1;y<=n;y++){
			int z=mapp[x][y];
			if(dis[y]>dis[x]+z&&vis[y]!=1){
				dis[y]=dis[x]+z;
				q.push(make_pair(-dis[y],y)); 
			}
		}
	} 
}
int main(){
	scanf("%lld%lld%lld",&n,&m,&d);
	memset(mapp,0x7f,sizeof(mapp));
	for(ll i=0;i<m;i++){
		ll x,y,z;
		scanf("%lld%lld%lld",&x,&y,&z);
		mapp[x][y]=min(z,mapp[x][y]);
	}
	for(int i=1;i<=n;i++)
		mapp[i][i]=0;
	dijkstra();
	for(int i=1;i<=n;i++)
		ans+=dis[i];
	upside_down();
	dijkstra();
	for(int i=1;i<=n;i++)
		ans+=dis[i];
	printf("%lld",ans+(n-1)*d*2);
	return 0;
}

T3

傳送門:https://www.luogu.com.cn/problem/U121284?contestId=31441

又雙叒叕是一個圖論題!

\(50pts\)

仍然先感謝學長不殺之恩:對於\(50\)%的資料,\(n\)<=100,所以我如果沒猜錯的話,這\(50pts\)應該是給了暴搜吧……但是好像機房裡沒有人願意拿這個部分分唉?

\(100pts\)

掃一眼題面,我們瞭解到,本題要求出從\(s\)->\(e\)的一條路徑,使得這條路徑上,最大的邊權最小,並輸出這個最大邊權最小值

想了一通有沒有這麼一個演算法來應對這種問題,,,但是顯然沒有

突然想到,本題的答案似乎具有單調性,可以使用二分法求解

具體怎麼個單調性?

首先,我們二分一箇中點\(Mid\),然後在圖上跑一遍,如果遇到權值比\(Mid\)值要大的邊,就自動忽視掉

然後,看看剩下的這些邊,能不能支援我們從\(s\)->\(e\)

如果不走比\(Mid\)權值大的邊,可以支援我們從\(s\)->\(e\),說明那個最大邊權最小值比\(Mid\)小,反之,比\(Mid\)

然而還有一個問題,我們怎麼判斷這兩個點在上述二分求解的情況中是否被聯通呢?

一開始我想到了\(dfs\),但是經過我多年寫暴搜\(TLE\)的慘痛教訓,我真的不願意寫這個搜尋了。。。

於是我又想起了一個玄學做法——跑\(dijkstra\),首先賦值\(dis\)陣列為\(0x7f7f7f7f\),從\(s\)跑一遍\(dijkstra\),看看\(e\)的最短路有沒有被更新,如果被更新了,說明\(s\),\(e\)聯通,反之,\(s\)\(e\)不連通

然後我把這兩個玄學的思路結合到了程式碼裡:

\(Code\)

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
const int N=5005;
typedef long long int ll;
struct edge{
	int to,cost;
};
vector<edge>v[N];
priority_queue< pair<int,int> >q;
ll dis[N],vis[N],cop[N],n,m,s,e;
bool dijkstra(int a,int m){
	memset(dis,0x7f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[a]=0;
	q.push(make_pair(0,a));
	while(q.size()!=0){
		int x=q.top().second;
		q.pop();
		for(unsigned int i=0;i<v[x].size();i++){
			int y=v[x][i].to,z=v[x][i].cost;
			if(z>m) continue;
			if(dis[y]>dis[x]+z){
				dis[y]=dis[x]+z;
				q.push(make_pair(-dis[y],y));
			}
		}
	}
	if(dis[e]>2147483646) return false;
	else return true;
}
int main(){
	scanf("%lld%lld",&n,&m);
	for(ll i=0,x,y,z;i<m;i++){
		scanf("%lld%lld%lld",&x,&y,&z);
		v[x].push_back((edge){y,z});
		v[y].push_back((edge){x,z});
	}
	scanf("%lld%lld",&s,&e);
	ll l=0,r=1000000,ans=1000000;
	while(l<=r){
		ll Mid=(l+r)>>1;
		if(dijkstra(s,Mid)==true){
			r=Mid-1;
			ans=min(ans,Mid);
		}else l=Mid+1;
	}
	printf("%lld",ans);
	return 0;
}

結果\(AC\)了,比賽後學長髮了一下題解,正解是用\(Kruscal\)最小生成樹演算法做的,然後因為我不會\(Kruscal\)所以這裡直接貼一波\(std\)叭!

\(std\) \(Code\)

#include<iostream>
#include<cstdio>
#include<queue>
#include<cctype>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
inline int read()
{
    int a=0,b=1;
    char c=getchar();
    while(!isdigit(c))
    {
        if(c=='-')
            b=-1;
        c=getchar();
    }
    while(isdigit(c))
    {
        a=(a<<3)+(a<<1)+(c^48);
        c=getchar();
    }
    return a*b;
}
int num,s,t,maxn,father[5005];
struct edge
{
    int from,to,dis;
}e[400005];
bool cmp(edge a,edge b)
{
    return a.dis<b.dis;
}
int getfather(int x)
{
    if(father[x]==x)
        return x;
    father[x]=getfather(father[x]);
    return father[x];
}
int n,m,cnt;
long long ans;
int main()
{
//  freopen("486.in","r",stdin);
//  freopen("486.out","w",stdout);
    int n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        e[i].from=read(),e[i].to=read(),e[i].dis=read();
    }
    sort(e+1,e+m+1,cmp);
    s=read(),t=read();
    for(int i=1;i<=n;i++)
    {
        father[i]=i;
    }
    for(int i=1;i<=m&&cnt<=n-1;i++)
    {
        int fu=getfather(e[i].from),fv=getfather(e[i].to);
        if(fu==fv)
            continue;
        father[fu]=fv;
        cnt++;
        ans+=e[i].dis;
        if(getfather(s)==getfather(t))
        {
            printf("%d\n",e[i].dis);
            return 0;
        }
    }
    return 0;
} 

##其實有T4,但是是一個毒瘤高精+動規,所以勞資不寫啦!!!