1. 程式人生 > >[luogu]P2680 運輸計劃

[luogu]P2680 運輸計劃

答案 nlog 進行 ever == problem choose () space

原題鏈接 :P2680 運輸計劃

分析

題意很簡單,給定一張連通圖,n個點,n-1條邊,很顯然是一棵樹。
現在給定m條鏈\((u,v)\)
現在可以cut掉一條邊(邊權置為0)。
現在求最長鏈的最小值。

55pts

蒟蒻的我肯定是拿不到滿分的。。我們直接考慮部分分。
對於m=1的點,我們只需要枚舉鏈上的全部邊,然後cut掉最長的那一個就可以了。
裸搜一遍dfs,然後求出鏈長,最後減去最長邊就可以了。
然後是第i條航線鏈接i和i+1點的點。
很明顯整張圖形成了一條鏈。
前綴和維護整張圖,這樣子可以把查詢優化到\(O(1)\)
然後枚舉最長鏈上的每一條邊,考慮把它cut掉之後的答案,然後求一下最小值即可。

這樣子55pts就輕松到手了,時間不到20分鐘。

100pts

但現在不是考場啊!!!
所以我們要考慮正解啊。
首先考慮怎麽在圖上求出每一條鏈的長度。
我們可以利用LCA求出每兩個點的公共祖先,在求的同時枚舉每一個點到1號點的距離(要是怕卡的話可以隨機化初始節點)。
然後鏈\((u,v)\)的長度就是\(dis_u+dis_v-2\times dis_{lca_{(u,v)}}\)
這樣子我們就可以O(1)查詢每條鏈的長度了。
然後直接暴力枚舉最長鏈上的邊,然後刪邊就可以了。
...
...
哪有這麽簡單。
求LCA的時間復雜度為\(O(nlogn)\)而暴力枚舉的最壞復雜度為\(O(n^2)\)

這樣子肯定是通不過全部的數據的。
我們考慮要降低這個暴力枚舉的復雜度。
由於我們只需要求最長鏈,我們可以考慮二分優化。
二分出最長鏈的長度mid,然後我們只需要對大於這個長度的鏈進行考慮就可以了。
我們對每一條邊求出它和所需要的長度的差值,並且統計出最大的差值。
然後我們就要對大於這個差值的邊進行考慮。
由於只有在鏈上的邊被刪除了才有用,我們就需要找出是否有(大於這個最大差值並且在所有長度大於mid的鏈上)的邊。
暴力枚舉每一條邊?
時間復雜度又變回\(O(n^2)\)了,行不通。
那麽我們就考慮一個\(O(n)\)的算法。
發現之前LCA的dfs裏可以統計出每個節點的dfn(dfs)序,而這個序是非常有用的。
我們可以發現每一個節點的子節點都在它的後面,也就是說只要從後往前推,我們就可以不重不漏地一遍更新所有的節點。
考慮利用樹上差分思路。
我們給每一條鏈的起點和終點打上添加標記(+1),在他們的LCA上打上刪除標記(-2),這樣我們在上推的時候就可以在起點和終點處施加影響,然後在他們的LCA處消除影響,我們就實現了\(O(n)\)統計每一條邊在鏈集裏的出現次數。
然後\(O(m)\)跑過每一條邊,如果發現邊的出現次數等於邊集的大小並且邊的大小大於之前所求出的最大差值,我們直接返回1,如果遍歷完所有的邊都沒有的話,我們返回0。
這樣子就可以在\(O(nlogn)\)的時間復雜度內解決問題了。
下面給出55pts和100pts的代碼。。

代碼

55pts

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stdlib.h>
using namespace std;
const int Maxn=300009;
int read(){
    char c;int num,f=1;
    while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
    while(c=getchar(), isdigit(c))num=num*10+c-'0';
    return f*num;   
}
int u,v,w;
int n,m,d[Maxn],len=0,le=0,flag;
int ed[Maxn][3],fa[Maxn],f[Maxn];
int head[Maxn],edge[Maxn],nxt[Maxn],ver[Maxn],tot=1;
void dfs(int x,int k){
    if(x==v){
        printf("%d\n",k-le);
        exit(0);
    }
    //printf("%d\n",x);
    f[x]=1;
    for(int i=head[x];i;i=nxt[i]){
        int tmp=le;
        int y=ver[i];
        if(!f[y]){
            le=max(edge[i],le);
            dfs(y,k+edge[i]);
        }
        le=tmp;
    }
}
void work1();
void work2();
int main()
{
    n=read();m=read();
    if(m==1)work1();
    else work2();
    return 0;
}
void work1(){
    for(int i=1;i<n;i++){
        u=read();v=read();w=read();
        ver[++tot]=v;nxt[tot]=head[u];edge[tot]=w;head[u]=tot;
        ver[++tot]=u;nxt[tot]=head[v];edge[tot]=w;head[v]=tot;
    }
    u=read();v=read();
    dfs(u,0);
}
void work2(){
    int k=0,ans=0x3f3f3f3f;
    for(int i=2;i<=n;i++){
        read();read();d[i]=read();
        d[i]+=d[i-1];
    }
    for(int i=1;i<=m;i++){
        u=read();v=read();
        ed[i][1]=u;
        ed[i][2]=v;
        if(!k||d[v]-d[u]>d[ed[k][2]]-d[ed[k][1]])k=i;
    }
    for(int i=ed[k][1]+1;i<=ed[k][2];i++){
        int cut=d[i]-d[i-1],f;
        for(int i=1;i<=m;i++){
            f=(ed[k][1]<i&&ed[k][2]>=i);
            ans=min(ans,d[ed[k][2]]-d[ed[k][1]]-cut*f);
        }
    }
    printf("%d\n",ans);
    
}
/* There is n nodes and the graph is connected ,we can easily know it is a tree
 * And there is m chains that connect u and v 
 * Our task is choose an edge and make its right be 0 ,making the ;ongest chain shorest
 * Get part marks:
 * Edge i connect i and i+1.
 * The graph become a chain.
 * We can easily get the marks.
 * Consider we must cut the edge on the longest chain.
 */

100pts

#include <bits/stdc++.h>
using namespace std;
const int Maxn=300009*2;
struct QAQ{
    int u,v,lcaa,diss;
}t[Maxn];
int read(){
    char c;int num,f=1;
    while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
    while(c=getchar(), isdigit(c))num=num*10+c-'0';
    return f*num;
}
int n,m,u,v,w,cnt,num[Maxn],deep[Maxn],vis[Maxn],fa[Maxn][30],tmp[Maxn];
int head[Maxn],edge[Maxn],nxt[Maxn],ver[Maxn],tot=1,dis[Maxn];
void dfs(int x,int dep){
    num[++cnt]=x;
    deep[x]=dep;
    vis[x]=1;
    for(int i=1;i<25;i++)
        fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int i=head[x];i;i=nxt[i])if(!vis[ver[i]]){
        fa[ver[i]][0]=x;
        dis[ver[i]]=dis[x]+edge[i];
        dfs(ver[i],dep+1);
    }
}
int lca(int x,int y){
    if(deep[x]<deep[y])swap(x,y);
    int t=deep[x]-deep[y];
    for(int i=0;i<25;i++)
        if((1<<i)&t)x=fa[x][i];
    if(x==y)return x;
    for(int i=24;i>=0;i--)
        if(fa[x][i]!=fa[y][i])
            x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
bool check(int maxn){
    int cnt=0,ans=0;
    memset(tmp,0,sizeof(tmp));
    for(int i=1;i<=m;i++)if(t[i].diss>maxn){
        tmp[t[i].u]++;tmp[t[i].v]++;tmp[t[i].lcaa]-=2;
        ans=max(ans,t[i].diss-maxn);
        cnt++;
    }
    if(!cnt)return true;
    for(int i=n;i>=1;i--)tmp[fa[num[i]][0]]+=tmp[num[i]];
    for(int i=2;i<=n;i++)if(tmp[i]==cnt&&dis[i]-dis[fa[i][0]]>=ans)return true;
    return false;
}
int main()
{
    //freopen("data.in","r",stdin); 
    int summ=0;
    n=read();m=read();
    for(int i=1;i<n;i++){
        u=read();v=read();w=read();
        ver[++tot]=v;nxt[tot]=head[u];edge[tot]=w;head[u]=tot;
        ver[++tot]=u;nxt[tot]=head[v];edge[tot]=w;head[v]=tot;
        summ+=w;
    }
    dis[1]=0;
    dfs(1,1);
    //cout<<lca(4,5)<<endl;
    //dfs for one ,remark every node ,and Mul array.
    for(int i=1;i<=m;i++){
        u=read();v=read();  
        t[i].u=u;t[i].v=v;
        t[i].lcaa=lca(u,v);
        t[i].diss=dis[u]+dis[v]-2*dis[t[i].lcaa];
        //cout<<t[i].diss<<endl;
    }
    //get each node's father
    int l=0,r=summ,mid;
    while(l<=r){
        mid=(l+r)>>1;
        if(check(mid))r=mid-1;
        else l=mid+1;
    }
    printf("%d\n",l);
    return 0;
}

[luogu]P2680 運輸計劃