NOIP2013·洛谷·货车运输

初见安~这里是传送门:洛谷P1967

题目描述

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

输入格式:

第一行有两个用一个空格隔开的整数n,mn,m,表示 AA 国有nn 座城市和 mm 条道路。

接下来 mm行每行33个整数 x, y, zx,y,z,每两个整数之间用一个空格隔开,表示从 xx号城市到yy号城市有一条限重为 zz 的道路。注意: xx 不等于 yy,两座城市之间可能有多条道路

接下来一行有一个整数 q,表示有 q 辆货车需要运货。

接下来 q 行,每行两个整数 x、y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,注意: x 不等于 y 

输出格式:

共有 qq 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货车不能到达目的地,输出-1−1。

输入样例#1: 

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

输出样例#1: 

3
-1
3

说明

对于 30\%30%的数据,0 < n < 1,000,0 < m < 10,000,0 < q< 1,0000<n<1,000,0<m<10,000,0<q<1,000;

对于 60\%60%的数据,0 < n < 1,000,0 < m < 50,000,0 < q< 1,0000<n<1,000,0<m<50,000,0<q<1,000;

对于 100\%100%的数据,0 < n < 10,000,0 < m < 50,000,0 < q< 30,000,0 ≤ z ≤ 100,0000<n<10,000,0<m<50,000,0<q<30,000,0≤z≤100,000。

题解

首先一个图,给你两个点,让你找到一条路径使得路径上最小的边权最大。。直接对于每条路径找很麻烦,但是对于整个图来说,使其最小边权最大化,就很容易了——求最大生成树就行了。这一步操作想必是没有问题的。并且为了求出来过后我们还要接着操作,我们需要得到生成的所有边并存图,所以只能用Kruskal求最大生成树。

在此基础上,每两点之间的路径就唯一了。接下来我们要维护任意两点直接的路径上的最小边权,一看就是要用到LCA的。这里各位可以选择直接用倍增维护,写起来也是比较简单的【but我写不惯……】。这里我是采用的树剖+线段树来维护的,所以代码量看起来比较长【事实上都是些模板操作……】边化点的详细操作树剖传送门里有。

然后就没有了。【??!是不是思路说白了就两步,很简单?

至于判定无解-1,直接在Kruskal找到了各个点所在的并查集的基础上询问祖先节点是否相同即可。

可能写倍增不用考虑这么多【因为没有看到题解有人声明过这个细节】,但是还有一个极大的坑——最后一组毒瘤数据,我的代码爆了MLE,与此同时在bzoj的入门OJ上爆了RE。找了好久的原因才终于意识到——如果给你的图天生就不连通,而货车要运输的两点在同一个连通块呢?意思就是,你如果dfs标记只标记一次,那么你只会处理完其中一个连通块,剩下的都没有标记,编号都是0,以至于即便能到达,相连的边被选中了的,你也会因为树剖dfn标记异常而在查询的时候while卡爆。所以——要开一个for来确保标记了整个图……QAQ

下面上代码和详解!!!

#include<bits/stdc++.h>
#define maxn 10005
#define maxm 50005
using namespace std;
int read()
{
    register int x = 0, f = 1, ch = getchar();
    while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
    while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
    return x * f;
}

int n, m;
struct node
{
    int u, v, w;
    bool operator < (const node &x) const
    {
        return x.w < w;
    }
}g[maxm];

int fa[maxn];
int get(int x)
{
    if(fa[x] == x) return x;
    return fa[x] = get(fa[x]);
}

struct edge
{
    int to, w, nxt;
    edge(){}
    edge(int tt, int ww, int nn)
    {
        to = tt, w = ww, nxt = nn;
    }
}e[maxn << 1];

int k = 0, head[maxn];
void add(int u, int v, int w)
{
    e[k] = edge(v, w, head[u]);
    head[u] = k++;
}

int dep[maxn], size[maxn], son[maxn], val[maxn], fa_[maxn];
void dfs_1(int u)//树剖初始化1
{
    size[u] = 1;
    for(register int i = head[u]; ~i; i = e[i].nxt)
    {
        register int v = e[i].to, w = e[i].w;
        if(v == fa_[u]) continue;
        dep[v] = dep[u] + 1;
        fa_[v] = u;
        dfs_1(v);
        size[u] += size[v];
        if(size[v] > size[son[u]]) son[u] = v;
    }
}

int top[maxn], dfn[maxn], tot = 0;
void dfs_2(int u, int tp)//树剖初始化2
{
    top[u] = tp;
    dfn[u] = ++tot;
    if(son[u]) dfs_2(son[u], tp);
    for(register int i = head[u]; ~i; i = e[i].nxt)
    {
        register int v = e[i].to, w = e[i].w;
        if(v != fa_[u] && v != son[u]) dfs_2(v, v);
        val[dfn[v]] = w;
    }
}

int road[maxn << 2];
void build(int p, int l, int r)//线段树建树
{
    if(l == r)
    {
        road[p] = val[l];
        return;
    }
    register int mid = l + r >> 1;
    build(p << 1, l, mid);
    build(p << 1 | 1, mid + 1, r);
    road[p] = min(road[p << 1], road[p << 1 | 1]);
}

int ask(int p, int l, int r, int ls, int rs)//线段树查询
{
    if(ls <= l && r <= rs) 
        return road[p];
    }
    register int mid = l + r >> 1, ans = 1 << 30;
    if(ls <= mid) ans = min(ans, ask(p << 1, l, mid, ls, rs));
    if(rs > mid) ans = min(ans, ask(p << 1 | 1, mid + 1, r, ls, rs));
    return ans;
}

void work(int u, int v)//树剖基本操作
{
    register int ans = 1 << 30;
    register int tmp1 = u, tmp2 = v;
    while(top[u] != top[v])
    {   
        if(dep[top[u]] > dep[top[v]]) swap(u, v);
        ans = min(ans, ask(1, 1, n, dfn[top[v]], dfn[v]));
        v = fa_[top[v]];
    }
    if(dep[u] > dep[v]) swap(u, v);
    ans = min(ans, ask(1, 1, n, dfn[son[u]], dfn[v]));
    printf("%d\n", ans);
}

int main()
{
    n = read(), m = read();
    for(register int i = 1; i <= n; i++)
        fa[i] = i;
    for(register int i = 1; i <= m; i++)
        g[i].u = read(), g[i].v = read(), g[i].w = read();
        
    sort(g + 1, g + 1 + m);
    memset(head, -1, sizeof head);
    register int cnt = 0;//最大生成树开始
    for(register int i = 1; i <= m; i++)
    {
        register int u = g[i].u, v = g[i].v, w = g[i].w;
        if(get(u) == get(v)) continue;
        fa[get(u)] = get(v);
        add(u, v, w);
        add(v, u, w);
        cnt++;
        if(cnt == n - 1) break;//省时处理
    }
    
    for(int i = 1; i <= n; i++)//这里一定要开for!!!QAQ
    	if(!size[i]) dfs_1(i);
    	
    for(int i = 1; i <= n; i++)//这里也一定要开for!!!QAQ
    	if(!top[i]) dfs_2(i, i);
    val[1] = 1 << 30;//赋值
    build(1, 1, n);
    
    register int q, u, v;
    q = read();
    while(q--)
    {
        u = read(), v = read();
        if(get(u) != get(v))//判定不连通情况
        {
            puts("-1");continue;
        }
        work(u, v);
    }
}

真的佩服自己有耐心写这么长的代码还debug了这么久……

迎评:)
——End——

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值