【NOIP2015提高組Day2】運輸計劃

按慣例發題……

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

分析:

其實題目說那麼多,一句話就是:給定一棵帶權樹與mm條路徑,你可以使一條樹上的邊的權值變爲0,問你mm條路徑的長度的最大值最小是多少。

這道題讓我想到了貨車運輸這道題,但是更難,但方法可以借鑑。

因爲這是最大值最小問題,很顯然可以二分答案。
那這個二分判斷怎麼打呢?
我們如果遇到某條邊,所有超時的邊(即超過當前二分的答案)都經過此邊,且最長路徑當前邊的權值小於等於二分的答案(即把這條邊的權值變爲0),就證明答案合法。
否則不合法。

那麼我們再往下分析,怎麼求某條邊被所有超時的邊經過?我們可以想到樹上差分!(關於樹上差分,以後會補更,這裏麻煩大家先自己去學,挺簡單的),把超時的路徑加一,最後跑一遍每一條邊,判斷用上文提到的方法。

我們又想,怎麼知道哪條邊超時了?沒錯,就是LCA!(LCA鏈接)(我們這裏就用倍增,不用樹鏈剖分了)
想必LCA求出來了,求距離也很容易了吧。
只要計算:len[i]=dis[a[i]]+dis[b[i]]2dis[lca[i]]len[i]=dis[a[i]]+dis[b[i]]-2*dis[lca[i]](即當前距離等於a[i]a[i]到根的距離b[i]b[i]到根的距離減去它們LCA到根的距離

形象畫圖:在這裏插入圖片描述
這裏稍微要注意一下,這裏的邊帶了權值,所以計算時不可以用深度depdep,而是加入一個距離disdisdisdis的更新可以在LCA遞歸預處理時把當前的邊記錄,即賦值disdis爲邊權。

從95到100

我發現許多人都卡在95分,被卡常,差幾毫秒超時了,這裏提供幾種優化:
1.用快讀(快讀鏈接
2.在樹上差分時用dfsdfs序處理(雖然理論上是一樣的,但是循環的常數比遞歸的小)這裏可以在LCA遞歸預處理時記錄dfsdfs序。

代碼:

#include<iostream>  
#include<cstdio>    
#include<cstring>    
#define N 300010    
using namespace std;    
int n,m,x,y,z,MAX,l,r,mid,ans;    
int a[N],b[N],lca[N],len[N],cf[N];    
int f[N][21],dfn[N],dfn_tot,dep[N],dis[N],va[N],father[N];  
int head[N],next[N*2],e[N*2],v[N*2],tot;//鏈式前向星
inline void read(int &x)//快讀加速
{  
    x=0;char c=getchar();  
    while (c<'0'||c>'9') c=getchar();  
    while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();  
}   
inline int add(int x,int y,int z) {e[++tot]=y,v[tot]=z,next[tot]=head[x],head[x]=tot;}//鏈式前向星
inline void dfs(int x,int fa)//LCA遞歸預處理
{  
    dfn[++dfn_tot]=x/*dfs序*/,father[x]=fa,dep[x]=dep[fa]+1;
	for (register int i=0;i<20;i++) f[x][i+1]=f[f[x][i]][i];  
    for (register int i=head[x];i;i=next[i])  
        if (e[i]!=fa) dis[e[i]]=dis[x]+v[i]/*dis數組更新*/,f[e[i]][0]=x,va[e[i]]=v[i],dfs(e[i],x);  
}  
int LCA(int x,int y)//求LCA
{  
    if (dep[x]<dep[y]) swap(x,y);  
    for (register int i=20;i>=0;i--)  
    {  
        if (dep[f[x][i]]>=dep[y]) x=f[x][i];  
        if (x==y) return x;  
    }  
    for (register int i=20;i>=0;i--)  
        if (f[x][i]&&f[y][i]&&f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];  
    return f[x][0];  
}  
inline int check(int mid)//二分判斷函數
{ 
    memset(cf,0,sizeof(cf)); 
    int k=0; 
    for (register int i = 1;i <= m; ++i) 
        if (len[i]>mid) cf[a[i]]++,cf[b[i]]++,cf[lca[i]]-=2,k++;//樹上差分
    for (register int i=n;i;i--)//樹上差分dfs序優化
        cf[father[dfn[i]]]+=cf[dfn[i]]; 
    for (register int i=1;i<=n;i++)//枚舉邊判斷
        if (cf[i]==k&&MAX-va[i]<=mid) return 1; 
    return 0; 
} 
int main()    
{    
    freopen("transport.in","r",stdin);    
    freopen("transport.out","w",stdout);    
    read(n), read(m);  
    for (register int i=1;i<n;i++) read(x),read(y),read(z),add(x,y,z),add(y,x,z);   
    dfs(1,0);  
    for (register int i=1;i<=m;i++) 
	{
		read(a[i]),read(b[i]);
		lca[i]=LCA(a[i],b[i]),len[i]=dis[a[i]]+dis[b[i]]-2*dis[lca[i]]/*計算距離*/,MAX=max(MAX,len[i])/*求最大距離*/;  
	}
    r=MAX;//二分    
    while (l<=r)
    {    
        if (check(mid=l+r>>1)) r=mid-1,ans=mid;    
        else l=mid+1;    
    }   
    printf("%d",ans);    
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章