洛谷1967 货车运输 NOIP2013

Problem

Description

A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

Input Description

输入文件名为 truck.in。
输入文件第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道
路。 接下来 m 行每行 3 个整数 x、 y、 z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z 的道路。注意: x 不等于 y,两座城市之间可能有多条道路 。
接下来一行有一个整数 q,表示有 q 辆货车需要运货。
接下来 q 行,每行两个整数 x、y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,注意: x 不等于 y 。

Output Description

输出文件名为 truck.out。
输出共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货
车不能到达目的地,输出-1。

Sample Input

4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3

Sample Output

3
-1
3

Date Size

0 < n < 10,000,0 < m < 50,000,0 < q< 30,000,0 ≤ z ≤ 100,000。

Thoughts

刚开始看题目的时候,难度是[提高+/省选-],提交7.7K,通过2.3K,好像很简单的样子,赶紧敲一下去A题。于是我就开始做。这真是一个错误的决定。
首先可以把这条路的限重作为它的权值。最多有30000次询问,SPFA什么的是不要想了,那么也许可以跑floyd,对于这个图也许有独立的,那么还需要把n个点都判断一下,如果没有处理过,那就是另一个独立的图,再跑floyd……尽管如此,最坏情况10000个点在一起, On3 的暴力还是保证T成狗。想来是走不通的,只好另寻他法。

Solution

MST

题中提到,两座城市之中有多条路径,那么有一些边是必定可以直接略掉的,由此可以通过减少边来简化题目。然而这并没有什么用。继续感性地想一想,那么就会发现,要使得货车的载重最大,那就要选取尽量大的边去走,要找到这样的边来遍历整个可到达的点——跑最大生成树重新建图。在这个最大生成树上跑就必定是最优的,因为如果有一条路径权值更大,可以使得载重变大,那么这个权值必定比原最大生成树的某一边权值大,而这与最大生成树相冲突。
因为不知道这会生成几棵树,用kruskal的时候,所有的边都需要扫一遍,判断是否已经联通。

LCA

然后我们会提交,然后我们会再次TLE,然后我们会爆炸,然后我们就开始膜kb,boshi。虽然并没有什么用。
注意到,如果可达,那么这个货车的路径总是在树上的,对于同一棵树上的两个节点,求两点的权值和,那么我们就可以用到树上公共祖先的算法来优化。由于tarjan难以保证答案按顺序输出,那么就用*倍增。在预处理父亲节点的时候,顺便处理处这条路径上的最小权值。于是很愉快的我们的算法快了很多。
好不容易才A了道题,一点也不愉快,因为你会发现这个时候,你已经打了很长的代码了。

Code

#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=10010,maxm=50010,INF=0x3f3f3f3f;
template <typename Tp> inline void read(Tp &x)
{
    x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
struct data{
    int u,v,w;
}a[maxm];
struct node{
    int v,w,nxt;
}edge[maxm<<2];
int n,m,q,p=0,head[maxn],f[maxn],deep[maxn],dis[maxn][21],pre[maxn][21];
bool vis[maxn];
inline bool cmp(const data &x,const data &y){return x.w>y.w;}
inline int min(int x,int y){return x<y?x:y;}
int find(int x)
{
    if(f[x]==x)
      return x;
    return f[x]=find(f[x]);
}
void insert(int u,int v,int w)
{
    edge[++p].v=v;
    edge[p].w=w;
    edge[p].nxt=head[u];
    head[u]=p;
    edge[++p].v=u;
    edge[p].w=w;
    edge[p].nxt=head[v];
    head[v]=p;
}
void input()
{
    read(n),read(m);
    for(int i=1;i<=m;i++)
      read(a[i].u),read(a[i].v),read(a[i].w);
    read(q);
}
void dfs(int x)//倍增的预处理
{
    vis[x]=true;
    for(int i=1;i<=20;i++)
    {
        pre[x][i]=pre[pre[x][i-1]][i-1];
        dis[x][i]=min(dis[x][i-1],dis[pre[x][i-1]][i-1]);
    }
    for(int i=head[x];i;i=edge[i].nxt)
      if(!vis[edge[i].v])
      {
        pre[edge[i].v][0]=x;
        dis[edge[i].v][0]=edge[i].w;
        deep[edge[i].v]=deep[x]+1;
        dfs(edge[i].v);
      }
}
int getlca(int x,int y)
{
    if(deep[x]<deep[y])
      swap(x,y);
    int t=deep[x]-deep[y];
    for(int i=0;i<=20;i++)
      if(t&(1<<i))
        x=pre[x][i];
    if(x==y)
      return x;
    for(int i=20;i>=0;i--)
      if(pre[x][i]!=pre[y][i])
        x=pre[x][i],y=pre[y][i];
    return pre[x][0];
}
int query(int x,int lca)
{
    int t=deep[x]-deep[lca],res=INF;
    for(int i=0;i<=20;i++)
      if(t&(1<<i))
      {
        res=min(res,dis[x][i]);
        x=pre[x][i];
      }
    return res;
}
int main()
{
    int fx,fy,u,v,lca;
    input();
    for(int i=1;i<=n;i++)
      f[i]=i;
    sort(a+1,a+m+1,cmp);
    for(int ptr,i=1;i<=m;i++)
    {
        fx=find(a[i].u),fy=find(a[i].v);
        if(fx!=fy)
        {
            insert(a[i].u,a[i].v,a[i].w);//重新建树
            f[fx]=fy;
            ptr++;
        }
        if(ptr==n-1)//虽然可能是森林,但n-1依然是上界
          break;
    }
    for(int i=1;i<=n;i++)
      if(!vis[i])
        dfs(i);
    for(int i=1;i<=q;i++)
    {
        read(u),read(v);
        if(find(u)!=find(v))
        {
            puts("-1");
            continue;
        }
        lca=getlca(u,v);
        printf("%d\n",min(query(u,lca),query(v,lca)));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值