CH 6703 PKU ACM Team's Excursion 有向圖的必經邊或“橋”

~hsm~發表於2019-03-11

title

CH 6703 PKU ACM Team’s Excursion

總時間限制: 2000ms 記憶體限制: 65536kB
描述
It is a traditional activity for PKU ACM Team to go hiking during the summer training camp. However, this tradition has been abandoned for several years. To recover this meaningful and interesting hiking tradition, the coach is planning an excursion for this summer. In addition, the new team members strongly urged the team somewhere “exciting”. To satisfy their desire, the coach has found a national park and acquired the map of this park. In this map, there are n intersections (numbered from 0 to n-1) connected by m one-way roads. This map has an interesting property that you cannot go back to the intersection you stand now once you leave it. Thus, the map of the park is a typical directed acyclic graph!
So, why is this place exciting? Because some of the roads called “bridges” (they are not true bridges but a jargon referred to the special roads we will soon define below) are dangerous because the bridges may collapse when we walk along them. Don’t panic! The safety facilities are adequate and robust, and there is only mentally impact without physical danger. Just have fun!
The excursion will begin at a certain starting intersection s and end at a certain destination intersection t. You can find a route from intersection s to t. A road is called a “bridge” if and only if you cannot find a route from starting intersection to destination once it disappears. The other ordinary roads are not bridges so they are not dangerous at all. According to the experience of the tourists, the danger level of a “bridge” is equal to its length.
Some wicked guys want to make the excursion more dangerous. But wait! A girl is crying! What? A girl? Eventually, PKU ACM team recruited a girl this year! OK… Lady first is a long term policy in PKU ACM team. Boys and girls finally reach a compromise that the excursion should be as safe as possible.
The national park provides an “express” service. The hikers can order at most two buses beforehand to help them to escape some of the most dangerous routes. Each bus can start from anywhere (any intersection or any point on any road) in the national park. But the bus is powered by electricity so it can only travel no more than q meters continuously and the passenger should get off before the electricity runs out. Simply speaking, the team can choose two “express” routes (each starts from anywhere and ends at anywhere), neither of the two routes is more than q meters. When the team is on a bus, the danger level along the route can be ignored.
The problem is reduced to find a route from the start intersection to the end with the least sum of danger levels. On the way we can take two special buses to make our route safe. The routes of two buses can start from anywhere and end at anywhere but the whole team should be put into one bus at one time. Individual activity is forbidden.
輸入
The first line contains an integer T (1 ≤ T ≤ 10) – the number of test cases.
For each test case:
The first line contains five integers n, m, s, t and q (1 ≤ n ≤ 100 000, 1 ≤ m ≤ 200 000, 0 ≤ s,t < n, s ≠ t, 1 ≤ q ≤ 1 000 000 000), indicate the number of intersections, the number of roads, the starting intersection, the end intersection and the limited length of route of a single bus can travel respectively.
The next m lines have the information of all roads in the directed acyclic graph. Each line contains a triple (u,v,w) indicating a road from intersection u to intersection v with length w (1 ≤ w ≤ 1 000) meters.
輸出
For each test case, please output a single line containing an integer representing the total danger level of the optimal route. If there is no route from s to t, output “-1” instead.
樣例輸入
1
8 9 0 7 7
0 4 1
0 1 10
1 2 9
4 2 2
2 5 8
4 3 3
5 6 6
5 6 7
6 7 5
樣例輸出
1

analysis

做了這道題後,我才正式發現:有向圖也是有“橋”的,只不過定義不一樣罷了。

放定義:若從SSTT的每條路徑都經過一條邊xy(x,y),則稱這條邊是有向圖中從SSTT的必經邊或“橋”。

所以說,學東西還是學全面點的,否則就會像我一樣出現很尷尬的事。。。。。。qwq

好了,說點有用的,大家可以看到,有向圖的“橋”和無向圖的橋,概念不一樣,那應該也能猜到兩個的演算法會不太一樣,據《算階》上說:

“有向圖的必經點和必經邊是一個較難的問題。因為環上的點也可能是必經點,所以不能簡單地把強連通分量縮點後按照有向無環圖(DAGDAG)來處理。LenguarTarjanLenguar——Tarjan演算法通過計算DominatorTree支配樹(Dominator—Tree),能夠在ONlogNO(NlogN)的時間內求出從有向圖的指定起點出發,走到每個點的必經點集。這超出了我們的討論範圍,感興趣的讀者可以閱讀本書作者在全國資訊學奧林匹克競賽冬令營上的講義《圖連通性若干擴充問題探討》。”

不過,值得一提的是,我們有很簡單的方法計算有向無環圖(DAG)的必經點和必經邊:
1.在原圖中按照拓撲序進行動態規劃,求出起點SS到圖中每個點xx的路徑條數fs[x]fs[x]
2.在反圖上再次按照拓撲序進行動態規劃,求出每個點xx到終點TT的路徑條數ft[x]ft[x]

顯然,fs[t]fs[t]表示從SSTT的路徑總條數。根據乘法原理:
1.對於一條有向邊xy(x,y),若fs[x]ft[y]==fs[T]fs[x] * ft[y] == fs[T],則xy(x,y)是有DAGDAGSSTT的必經邊。
2.對於一個點,若fs[x]ft[x]==fs[T]fs[x] * ft[x] == fs[T],則xxDAGDAGSSTT的必經點。

路徑條數是一個指數級別的整數,通常超過了32位或64位整數的表示範圍。受HashHash思想的啟發,我們可以把路徑條數對一個大指數取模後再儲存到fsfsftft陣列中。這樣帶來的後果是有較小的概率會產生誤判。保險起見,若題目時限寬鬆,我們多選取幾個質數,分別作為模數進行計算。

ok,介紹完了這個知識點,就可以來看看這道題了(不要想著看程式碼,快200行哩,不理解的話,可能。。。。。。。。。)。

現,摘錄一下《算階》上的題解。

首先,用前面提到的計算路徑條數(並取模)的方法,求出從SSTT的所有“橋”。
根據貪心策略,因為“橋”是從SSTT的任意一條路徑都包含的邊,所以我們應該讓路徑上除了“橋”以外的部分儘量短。並且我們只關心長度,不關心“橋”以外的部分到底是哪些邊。所以我們可以求出從SSTT的任意一條最短路,考慮在這條最短路徑的什麼地方叫車,能使危險程度最小。

一條最短路其實就是一條“鏈”,我們要用兩個區間覆蓋鏈上兩段長度不超過qq的部分,使未被覆蓋的“橋”的長度之和最小。

如果只有一個區間,那麼是非常好做的。可以正著掃描一遍這條最短路,用動態規劃演算法求出ds[i]ds[i],表示從SS到最短路上的第ii個節點,只打一次車的最小危險程度。

類似地,還可以倒著掃描一遍,用動態規劃演算法求出dt[i]dt[i],表示從最短路上的第ii個節點到TT,只打一次車的最小危險程度。

最後,我們列舉“切點”ii,用ds[i]+dt[i]ds[i]+dt[i]更新答案即可。
在這裡插入圖片描述

code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=3e5+10,maxm=2e5+5;
const ll inf=0x7fffffff;
const ll mod=1e9+7;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
ll spot[maxm<<1],ver[maxm<<1],edge[maxm<<1],Next[maxm<<1],head[maxn],Out[maxn],len;
inline void add(ll x,ll y,ll z)
{
	spot[++len]=x,ver[len]=y,edge[len]=z,Next[len]=head[x],head[x]=len,++Out[y];
}
ll vc[maxm<<1],ec[maxm<<1],Nc[maxm<<1],hc[maxn],Otc[maxn],lc;
inline void addc(ll x,ll y,ll z)
{
	vc[++lc]=y,ec[lc]=z,Nc[lc]=hc[x],hc[x]=lc,++Otc[y];
}
ll n,m,s,t,q;
ll deg[maxn],fs[maxn];
inline void topsort1()
{
	memcpy(deg,Out,sizeof(Out));
	queue<ll>q;
	fs[s]=1;
	for (ll i=1;i<=n;++i)
		if (!deg[i])
			q.push(i);
	while (!q.empty())
	{
		ll x=q.front();
		q.pop();
		for (ll i=head[x];i;i=Next[i])
		{
			ll y=ver[i];
			fs[y]+=fs[x];
			fs[y]%=mod;
			if (!--deg[y]) q.push(y);
		}
	}
}
ll ft[maxn];
inline void topsort2()
{
	queue<ll>q;
	ft[t]=1;
	for (ll i=1;i<=n;++i)
		if (!Otc[i])
			q.push(i);
	while (!q.empty())
	{
		ll x=q.front();
		q.pop();
		for (ll i=hc[x];i;i=Nc[i])
		{
			ll y=vc[i];
			ft[y]+=ft[x];
			ft[y]%=mod;
			if (!--Otc[y]) q.push(y);
		}
	}
}
ll dist[maxn],pre[maxn];
inline void topsort3()
{
	queue<ll>q;
	for (ll i=1;i<=n;++i)
		if (!Out[i])
			q.push(i);
	while (!q.empty())
	{
		ll x=q.front();
		q.pop();
		for (ll i=head[x];i;i=Next[i])
		{
			ll y=ver[i],z=edge[i];
			if (dist[y]>dist[x]+z)
			{
				dist[y]=dist[x]+z;
				pre[y]=x;
			}
			if (!--Out[y]) q.push(y);
		}
	}
}
ll f[maxn],path[maxn],sum[maxn],cnt;
bool flag1[maxn];
inline void solve1()
{
	ll cur=1;
	for (ll i=1;i<=cnt;++i)
	{
		while (dist[path[i]]-dist[path[cur]]>q)
			++cur;
		f[i]=f[i-1];
		ll temp=sum[path[i]]-sum[path[cur]];
		if (flag1[path[cur]])
			temp+=q-(dist[path[i]]-dist[path[cur]]);
		f[i]=max(f[i],temp);
	}
}
ll g[maxn];
bool flag2[maxn];
inline void solve2()
{
	ll cur=cnt;
	for (ll i=cnt;i>=1;--i)
	{
		while (dist[path[cur]]-dist[path[i]]>q)
			--cur;
		g[i]=g[i+1];
		ll temp=sum[path[cur]]-sum[path[i]];
		if (flag2[path[cur]])
			temp+=q-(dist[path[cur]]-dist[path[i]]);
		g[i]=max(g[i],temp);
	}
}
inline void Clear()
{
	for (ll i=1;i<=n;++i)
		dist[i]=inf;
	memset(fs,0,sizeof(fs));
	memset(ft,0,sizeof(ft));
	memset(flag1,0,sizeof(flag1));
	memset(flag2,0,sizeof(flag2));
	memset(sum,0,sizeof(sum));
	memset(f,0,sizeof(f));
	memset(g,0,sizeof(g));
	memset(head,0,sizeof(head));
	memset(hc,0,sizeof(hc));
	len=lc=0;
}
int main()
{
	int T;read(T);
	while (T--)
	{
		read(n);read(m);read(s);read(t);read(q);
		++s,++t;
		Clear();
		for (ll i=1;i<=m;++i)
		{
			ll x,y,z;
			read(x);read(y);read(z);
			++x,++y;
			add(x,y,z);addc(y,x,z);
		}
		topsort1();
		topsort2();
		for (ll i=1;i<=len;++i)
		{
			ll x=spot[i],y=ver[i];
			if (fs[x]*ft[y]%mod==fs[t])
			{
				sum[y]=edge[i];
				flag1[y]=flag2[x]=1;
			}
		}
		dist[s]=0;
		topsort3();
		if (dist[t]==inf)
			puts("-1");
		else
		{
			cnt=0;
			ll p=t;
			do
			{
				path[++cnt]=p;
				p=pre[p];
			} while (p!=s);
			path[++cnt]=s;
			reverse(path+1,path+cnt+1);
			for (ll i=1;i<=cnt;++i)
				sum[path[i]]+=sum[path[i-1]];
			solve1();
			solve2();
			ll temp=sum[path[cnt]],ans=inf;
			for (ll i=1;i<=cnt;++i)
				ans=min(ans,temp-(f[i]+g[i]));
			printf("%lld\n",ans);
		}
	}
	return 0;
}

相關文章