[luogu]P2680 运输计划

原题链接 :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;
}

转载于:https://www.cnblogs.com/onglublog/p/9865112.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值