NOI2018 归程 解题记录

题目描述

本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定。 魔力之都可以抽象成一个 n 个节点、 m 条边的无向连通图(节点的编号从 1至 n )。我们依次用 l,a 描述一条边的长度、海拔。 作为季风气候的代表城市,魔力之都时常有雨水相伴,因此道路积水总是不可避免 的。由于整个城市的排水系统连通,因此有积水的边一定是海拔相对最低的一些边。我们用水位线来描述降雨的程度,它的意义是:所有海拔不超过水位线的边都是有积水的。

Yazid 是一名来自魔力之都的OIer,刚参加完ION2018 的他将踏上归程,回到他 温暖的家。 Yazid 的家恰好在魔力之都的 1号节点。对于接下来 Q天,每一天Yazid 都会告诉你他的出发点 v,以及当天的水位线 p。 每一天,Yazid 在出发点都拥有一辆车。这辆车由于一些故障不能经过有积水的边。 Yazid 可以在任意节点下车,这样接下来他就可以步行经过有积水的边。但车会被留在他下车的节点并不会再被使用。 需要特殊说明的是,第二天车会被重置,这意味着:

车会在新的出发点被准备好。
Yazid 不能利用之前在某处停放的车。 Yazid 非常讨厌在雨天步行,因此他希望在完成回家这一目标的同时,最小化他步行经过的边的总长度。请你帮助 Yazid 进行计算。 本题的部分测试点将强制在线,具体细节请见【输入格式】和【子任务】。

【输入格式】

从文件 return.in 中读入数据。
单个测试点中包含多组数据。输入的第一行为一个非负整数 T,表示数据的组数。
接下来依次描述每组数据,对于每组数据:
• 第一行 2 个非负整数 n,m,分别表示节点数、边数。
• 接下来 m 行,每行 4 个正整数 u,v,l,a,描述一条连接节点 u,v 的、长度为 l、海
拔为 a 的边。
– 在这里,我们保证 1 ≤ u,v ≤ n。
• 接下来一行 3 个非负数 Q,K,S,其中 Q 表示总天数,K ∈ {0,1} 是一个会在下面
被用到的系数,S 表示的是可能的最高水位线。
• 接下来 Q 行依次描述每天的状况。每行 2 个整数 v 0 , p 0 描述一天:
– 这一天的出发节点为 v = (v 0 + K × lastans − 1) mod n + 1。
– 这一天的水位线为 p = (p 0 + K × lastans) mod (S + 1)。
– 其中 lastans 表示上一天的答案(最小步行总路程)。特别地,我们规定第 1
天时 lastans = 0。
– 在这里,我们保证 1 ≤ v 0 ≤ n,0 ≤ p 0 ≤ S。
对于输入中的每一行,如果该行包含多个数,则用单个空格将它们隔开。

【输出格式】

输出到文件 return.out 中。
依次输出各组数据的答案。对于每组数据:
• 输出 Q 行每行一个整数,依次表示每天的最小步行总路程。

【样例 1 输入】

1
4 3
1 2 50 1
2 3 100 2
3 4 50 1
5 0 2
3 0
2 1
4 1
3 1
3 2

【样例 1 输出】

0
50
200
50
150

【样例 1 解释】

第一天没有降水,Yazid 可以坐车直接回到家中。
第二天、第三天、第四天的积水情况相同,均为连接 1,2 号节点的边、连接 3,4 号
点的边有积水。
对于第二天,Yazid 从 2 号点出发坐车只能去往 3 号节点,对回家没有帮助。因此
Yazid 只能纯靠徒步回家。
对于第三天,从 4 号节点出发的唯一一条边是有积水的,车也就变得无用了。Yazid
只能纯靠徒步回家。
对于第四天,Yazid 可以坐车先到达 2 号节点,再步行回家。
第五天所有的边都积水了,因此 Yazid 只能纯靠徒步回家。

解题过程

考场上我这道题滚粗了啊~
先看到这道题,感觉题面怎么这么复杂啊……后来发现,可以先从1跑一边Dijkstra,最短路长度作为点权,然后对于每个询问,把权值大于p的边加入到图中,询问v所在的连通块中最小点权。
显然如果不强制在线的话是很好做的,把边按权值从大到小排序,然后询问按照权值从大到小排序,并查集中再维护连通块的最小点权即可。
如果在树上也好做,直接往祖先的部分倍增跳一下。
于是乎,我先打了个数据分治的暴力,打完之后发现已经过了快2h了,突然想到正解可持久化并查集……然后心中一凉,day1要挂啊……先不继续想了,往后看吧……
于是乎,别人这道题都A掉了,就我这么菜,脑子慢半拍,只水了80分跑掉~

正解——可持久化并查集

注意到如果强制在线的话,我们需要并查集支持的不过就是回退操作。我们可以不让并查集带路径压缩,用可持久化线段树维护并查集的father,然后暴力跳一跳就好了,复杂度 O ( n l o g 2 n ) ,王队长说数据不卡这个算法,而且似乎大部分人都写的这个吧……

官方正解——Kruskal重构树

首先我们可以给原题求一棵最大生成树,然后我们只需要在这棵树上维护连通性即可。
考虑合并两个连通块时,当前加入的边权为w,那么我们新建一个点,点权为w,左儿子和右儿子分别为两个连通块的根,当前点作为合并之后连通块的根。这样就会产生一棵二叉树,他有什么性质呢?
1.它是一个小根堆
2.原树中任意两点间路径上的最小边权等于这两个点在Kruskal重构树中LCA的点权。
于是乎,对于每一个询问,我们从叶子结点倍增向上跳,直到点权<=p,然后询问子树中所有叶子结点的最小点权(这里的点权指的是我们Dijkstra跑出来的点权)即为答案。
复杂度 O ( n l o g n )
这里就只放上官方正解的标程啦,毕竟可持久化并查集就是一个板子……(习惯性输入输出优化)

扫描二维码关注公众号,回复: 2619771 查看本文章
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> P;

const int maxn = 400005, INF = 0x3f3f3f3f;
struct Edge{int to, val, next;} edge[maxn * 2];
struct Sort{int u, v, w;} srt[maxn * 2];
int head[maxn], par[maxn], fa[20][maxn], mn[maxn], f[maxn], dis[maxn];
int rt[maxn], n, m, tot, T, K, S;
void addedge(int u, int v, int w){
    edge[++tot] = (Edge){v, w, head[u]};
    head[u] = tot;
}
priority_queue<P, vector<P>, greater<P> > pq;
void dijkstra(){
    memset(dis, 0x3f, sizeof(dis));
    pq.push(P(dis[1] = 0, 1));
    while(!pq.empty()){
        P p = pq.top(); pq.pop();
        int u = p.second;
        if(p.first > dis[u]) continue;
        for(int i = head[u]; i; i = edge[i].next){
            int v = edge[i].to;
            if(dis[v] > dis[u] + edge[i].val)
                pq.push(P(dis[v] = dis[u] + edge[i].val, v));
        }
    }
}
bool cmp(const Sort &a, const Sort &b){
    return a.w > b.w;
}
int find(int x){return x == par[x] ? x : par[x] = find(par[x]);}
const int maxr = 10000000;
char str[maxr], prt[maxr * 2]; int rpos, ppos;
char readc(){
    if(!rpos) fread(str, 1, maxr, stdin);
    char c = str[rpos++];
    if(rpos == maxr) rpos = 0;
    return c;
}
int read(){
    int x; char c;
    while((c = readc()) < '0' || c > '9');
    x = c - '0';
    while((c = readc()) >= '0' && c <= '9') x = x * 10 + c - '0';
    return x;
}
void print(int x, char c){
    if(x){
        static char sta[10];
        int tp = 0;
        for(; x; x /= 10) sta[tp++] = x % 10 + '0';
        while(tp > 0) prt[ppos++] = sta[--tp];
    } else prt[ppos++] = '0';
    prt[ppos++] = c;
}
int main(){
    //freopen("return.in", "r", stdin);
    //freopen("return.out", "w", stdout);
    T = read();
    while(T--){
        memset(head, 0, sizeof(head)); tot = 0;
        n = read(), m = read();
        for(int i = 0; i < m; i++){
            int u = read(), v = read(), w = read(), a = read();
            addedge(u, v, w);
            addedge(v, u, w);
            srt[i] = (Sort){u, v, a};
        }
        sort(srt, srt + m, cmp);
        dijkstra();
        memset(f, 0x3f, sizeof(f));
        for(int i = 1; i <= n; i++){
            rt[i] = par[i] = i;
            f[i] = dis[i];
            mn[i] = INF;
        }
        tot = n;
        for(int i = 0; i < m; i++){
            int u = srt[i].u, v = srt[i].v, w = srt[i].w;
            u = find(u), v = find(v);
            if(u != v){
                int p = ++tot;
                fa[0][rt[u]] = fa[0][rt[v]] = p;
                mn[rt[par[u] = v] = p] = w;
            }
        }
        for(int i = 1; i < tot; i++){
            int p = fa[0][i];
            if(f[p] > f[i]) f[p] = f[i];
        }
        for(int i = 1; i < 20; i++)
        for(int j = 1; j <= tot; j++)
            fa[i][j] = fa[i - 1][fa[i - 1][j]];
        m = read(), K = read(), S = read();
        int la = 0;
        while(m--){
            int v = read(), p = read();
            v = (v + K * la - 1) % n + 1;
            p = (p + K * la) % (S + 1);
            for(int i = 19; i >= 0; i--)
                if(mn[fa[i][v]] > p) v = fa[i][v];
            print(la = f[v], '\n');
        }
    }
    fwrite(prt, 1, ppos, stdout);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/WAautomaton/article/details/81267966
今日推荐