JZOJ 3348. 【NOI2013模擬】祕密任務(最短路+最小割唯一性)

JZOJ 3348. 【NOI2013模擬】祕密任務

題目

Description

在這裏插入圖片描述

Input

第一行 包含一個正整數T,表示有T組測試數據。接下來依次是T組測試數據。
每組測試數據的第一行包含兩個整N、M。
第二行包含 N - 1個正整數,依次表示 A1,A2, …,AN-1。
接下來 M行,每行爲三個整數:ui、vi、ci,表示一條連接城市ui和城市vi的路程等於ci的高速公路。

Output

輸出 T行, 依次表示每組測試數據的答案。若最優方案唯一則輸出“Yes”和最小代價,否則輸出“No”和最小代價。字符串和整數之間請用一個空格隔開 。

Sample Input

3
3 3
2 4
1 3 23
3 2 12
2 1 11
4 4
3 2 2
1 2 1
2 3 1
3 4 1
4 1 1
3 4
3 2
1 2 1
2 3 2
2 3 19
3 1 4

Sample Output

Yes 4
Yes 3
No 2

Hint

第 1組測試數據:最優 方案是在城市1設立兩個檢查點。
第 2組測試數據:最優方案是城市1的高速公路 (1, 4 )的出入口設立檢查點。
第 3組測試數據:最優方案是在城市2設立一個檢查點,不過既可以設置在高速公路(1, 2)的出入口,也可以設置在高速公路(2, 3)的出入口 。

Data Constraint

對於 10% 的數據: 2≤N≤10 , 1≤M≤20。
另有 40% 的數據: 最優方案是唯一的。
對於 100% 的數據: 2≤N≤400, 1≤M≤4000,1≤T≤5,1≤Ai, c≤10^9。無向圖可能有重邊 。

題解

  • 這是一道好題,內涵豐富,值得學習,並且反思總結。
  • 以下內容是詳細的思考過程,其中也包含不少他人的指導,十分可貴。
  • 首先分析題目,要求走的是最短路,那麼先建出最短路圖,
  • 方法一般爲,
  • nn開始做一邊單源最短路,然後從11開始遍歷,
  • 設當前的點爲xx,當前邊權值爲lnln,指向的點爲yy
  • 僅當dis[x]=dis[y]+lndis[x]=dis[y]+ln才說明這是最短路圖中的邊(易證),然後把這條邊加入最短路圖,接着走向yy繼續遍歷。
  • 其中要注意,因爲每個點與每條邊可能會走過多次,爲了防止重複,必須將已經過的點標記
  • 其實也可以用另一種方法代替這種遍歷,
  • 直接搜索每一條變,只要滿足dis[x]=dis[y]+lndis[x]=dis[y]+ln就加入最短路圖,
  • (不用判斷dis[y]=dis[x]+lndis[y]=dis[x]+ln,因爲如果這樣同一條邊會被加入兩次)
  • 記得上面的最短路圖是有向的
  • 題目要求11nn無法聯通,顯而易見,這就是求最小割模型,
  • 將最短路圖中每條邊的權值賦爲min(a[x],a[y])min(a[x],a[y]),當然,如果其中一個點爲nn,那麼權值只能取另一點的aa值,
  • 再根據最大流等於最小割,就可以計算出答案了。
  • 然而這樣並沒有結束,還需要判斷答案的唯一性,怎麼做?
  • 這就相當於判斷最小割的唯一性
  • 不一定!
  • 但首先需要判斷最小割的唯一性,
  • 可以找出所有必割的邊,當且僅當必割的邊權值之和爲最小割,才說明最小割的方案是唯一的,
  • 問題來了,哪些邊是必割的?
  • 有一個不是特別顯然的性質:
  • 如果有向邊(u,v)(u,v)必割則殘餘網絡中存在至少一條從11uu的路徑和一條從vvnn的路徑,
  • 不妨這樣想,如果不存在這樣的路徑,那麼(u,v)(u,v)就不是必割的(儘管不是特別嚴謹)。
  • 還有一種判斷的方法(來自網絡):
  • 設從11沿殘餘網絡能到達的點的集合爲SS,所有能沿殘餘網絡到達nn的點的集合爲TT,總的點集(不是讀入的,是最短網絡中的)爲VV
  • ST=VS∪T=V那麼不唯一,否則唯一。(感性理解)
  • 於是對於前一種判定的方法,先把11能走到的和能走到nn的標記上,
  • 搜一邊每條邊(不重不漏),符合條件(如上)的將邊權加入sumsum中,
  • 注意這裏因爲已經跑過了一邊網絡流,邊權的一部分可能會在其反向邊內,所以它的邊權應該是ln[i]+ln[i1]ln[i]+ln[i⊕1](符號是異或).
  • 如果sumanssum≠ans,那麼一定不唯一。
  • 一般的判斷最小割的唯一性,到此已經結束了,但這題還有特殊的地方,
  • 如果一條必割的邊(u,v)(u,v),滿足a[x]=a[v]a[x]=a[v],即說明關卡可以設在uu也可以設在vv,所以也是不唯一的。

代碼

#include<cstdio>
#include<cstring>
using namespace std;
#define N 410
#define M 4010
#define LL long long
int bz[N],q[M*10],n,m;
int to[M*2],next[M*2],last[N],len;
int to1[M*2],next1[M*2],last1[N],len1;
int cur[N],gap[N],ds[N];
LL a[N],ln[M*2],ln1[M*2],dis[N];
int bs[N],bt[N];
void add(int x,int y,LL c)
{
	to[++len]=y;
	ln[len]=c;
	next[len]=last[x];
	last[x]=len;
}
void add1(int x,int y,LL c)
{
	to1[++len1]=y;
	ln1[len1]=c;
	next1[len1]=last1[x];
	last1[x]=len1;
}
LL min(LL x,LL y)
{
	return x<y?x:y;
}
LL dg(int k,LL flow)
{
	if(k==n) return flow;
	LL have=0;
	for(int i=cur[k];i;i=next1[i]) 
		if(ds[to1[i]]+1==ds[k]&&ln1[i])
		{
			cur[k]=i;
			LL now=dg(to1[i],min(flow-have,ln1[i]));
			ln1[i]-=now,ln1[i^1]+=now,have+=now;
			if(flow==have) return have;
		}
	cur[k]=last1[k];
	if(!(--gap[ds[k]])) ds[1]=n;
	++gap[++ds[k]];
	return have;
} 
void dfs(int k)
{
	bs[k]=1;
	for(int i=last1[k];i;i=next1[i]) 
		if(ln1[i]&&dis[k]>dis[to1[i]]&&!bs[to1[i]]) dfs(to1[i]);
}
void dfs1(int k)
{
	bt[k]=1;
	for(int i=last1[k];i;i=next1[i]) 
		if(ln1[i^1]&&dis[k]<dis[to1[i]]&&!bt[to1[i]]) dfs1(to1[i]);
}
int main()
{
	int tn,i,j,x,y;
	LL c;
	scanf("%d",&tn);
	while(tn--)
	{
		memset(last,0,sizeof(last));
		memset(last1,0,sizeof(last1));
		len=len1=1;
		scanf("%d%d",&n,&m);
		for(i=1;i<n;i++) scanf("%lld",&a[i]);
		for(i=1;i<=m;i++)
		{
			scanf("%d%d%lld",&x,&y,&c);
			add(x,y,c),add(y,x,c);
		}
		memset(dis,127,sizeof(dis));
		memset(bz,0,sizeof(bz));
		dis[n]=0,bz[n]=1,q[1]=n;
		int l=0,r=1;
		while(l<r)
		{
			int k=q[++l];
			for(int i=last[k];i;i=next[i]) 
			{
				int g=to[i];
				if(dis[k]+ln[i]<dis[g])
				{
					dis[g]=dis[k]+ln[i];
					if(!bz[g]) bz[g]=1,q[++r]=g;
				}
			}
			bz[k]=0;
		}
		for(i=1;i<=n;i++)
			for(j=last[i];j;j=next[j]) if(dis[to[j]]+ln[j]==dis[i])
			{
				if(to[j]==n) add1(i,to[j],a[i]); else add1(i,to[j],min(a[i],a[to[j]]));
				add1(to[j],i,0);
			}
		memset(gap,0,sizeof(gap));
		memset(cur,0,sizeof(cur));
		memset(ds,0,sizeof(ds));
		gap[0]=n;
		LL s=0;
		while(ds[1]<n) s+=dg(1,dis[0]);
		int ok=1;
		memset(bs,0,sizeof(bs));
		memset(bt,0,sizeof(bt));
		dfs(1),dfs1(n);
		LL ss=0;
		for(i=1;i<=n;i++) if(ok)
			for(j=last1[i];j;j=next1[j]) 
			{
				int k=to1[j];
				if(!bs[i]||!bt[k]||dis[i]<dis[k]) continue;
				ss+=ln1[j]+ln1[j^1];
				if(k!=n&&a[i]==a[k]) 
				{
					ok=0;
					break;
				}
			}
		if(ss!=s) ok=0;
		if(ok) printf("Yes "); else printf("No ");
		printf("%lld\n",s);
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章