## [「NOI2018」归程](https://loj.ac/problem/2718)

题目描述

本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定. 魔力之都可以抽象成一个 >\(1\) 个节点、 \(m\) 条边的无向连通图(节点的编号从 \(1\) 至 \(n\) )。我们依次用 \(l, a\) 描述一

条边的长度、海拔。 作为季风气候的代表城市,魔力之都时常有雨水相伴,因此道路积水总是不

可避免 的。由于整个城市的排水系统连通,因此有积水的边一定是海拔相对最低的一些边。我们用水>位线来描述降雨的程度,它的意义是:所有海拔不超过水位线的边都是有积水的。

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

Yazid 非常讨厌在雨天步行,因此他希望在完成回家这一目标的同时,最小化他步行经过的边的总长度。请你帮助 Yazid 进行计算。 本题的部分测试点强制在线。

### 解题思路 :

观察发现,对于询问点能通过没有水的边能到达的点 \(u\),在此下车的答案就是 \(1\) 到它的最短路 \(dis_u\).

此时有一个很显然的离线做法,将询问的 \(p\) 从大到小排序,依次加边维护没有水的边的联通块,并维护每一个块的 \(dis\) 最小值即可

考虑强制在线就是把并查集可持久化,于是大力码一棵主席树即可以 \(O(nlog^2n)\) 的复杂度解决此题.

考虑到要可持久化的数组比较多,常数有点大,可以把联通块的 \(Mindis\) 以取反的形式存在 \(fa[rt]\) 上,可以少可持久化一个数组.

```cpp
#include
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a)
inline void read(T &x){
int f = 0, ch = 0; x = 0;
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
if(f) x = -x;
}
const int N = 700005, M = 1200005;
int n, m, Q, K, S;

int a[M], b[M], nxt[M], head[N], dis[N], cnt;

struct Node{

ll d, id;

bool operator < (const Node &A) const{ return d > A.d; }

}; priority_queue pq;

inline void add(int x, int y, int z){

a[++cnt] = y, b[cnt] = z, nxt[cnt] = head[x], head[x] = cnt;

}

inline void dijkstra(){

for(int i = 1; i <= n; i++) dis[i] = inf / 3;

dis[1] = 0, pq.push((Node){0, 1});

while(!pq.empty()){

Node now = pq.top(); pq.pop();

int val = now.d, u = now.id;

if(dis[u] < val) continue;

for(int p = head[u]; p; p = nxt[p]){

int v = a[p];

if(dis[u] + b[p] < dis[v]){

dis[v] = dis[u] + b[p];

pq.push((Node){dis[v], v});

}

}

}

}

struct PersistableArray{

int rt[N], s[M25], lc[M25], rc[M*25], size;

inline void clear(){

for(int i = 1; i <= size; i++) lc[i] = rc[i] = s[i] = 0;

for(int i = 1; i <= n; i++) rt[i] = 0; size = 0;

}

inline void build(int &u, int l, int r, int *a){

u = ++size;

if(l == r) return (void) (s[u] = a[l]);

int mid = l + r >> 1;

build(lc[u], l, mid, a), build(rc[u], mid + 1, r, a);

}

inline void Ins(int &u, int pr, int l, int r, int pos, int v){

u = ++size;

if(l == r) return (void) (s[u] = v);

int mid = l + r >> 1;

lc[u] = lc[pr], rc[u] = rc[pr];

if(pos <= mid) Ins(lc[u], lc[pr], l, mid, pos, v);

else Ins(rc[u], rc[pr], mid + 1, r, pos, v);

}

inline int query(int u, int l, int r, int pos){

if(l == r) return s[u];

int mid = l + r >> 1;

if(pos <= mid) return query(lc[u], l, mid, pos);

else return query(rc[u], mid + 1, r, pos);

}

inline int get(int u, int pos){ return query(rt[u], 1, n, pos); }

inline void mof(int u, int pos, int v){ Ins(rt[u], rt[u], 1, n, pos, v); }

}fa, sz;

namespace Dsu{

int a[N], ban;

inline int ask(int now, int x){

int p = fa.get(now, x);

if(p <= 0) return x; else return ask(now, p);

}

inline void unite(int x, int y){

int p = ask(ban, x), q = ask(ban, y);

if(p == q) return;

int szp = sz.get(ban, p), szq = sz.get(ban, q);

if(szp > szq) swap(p, q);

int vap = fa.get(ban, p), vaq = fa.get(ban, q);

fa.mof(ban, p, q), fa.mof(ban, q, Max(vap, vaq));

sz.mof(ban, q, szp + szq);

}

inline void addban(){

fa.rt[ban+1] = fa.rt[ban];

sz.rt[ban+1] = sz.rt[ban], ban++;

}

inline void prepare(){

ban = 0;

for(int i = 1; i <= n; i++) a[i] = -dis[i];

fa.build(fa.rt[0], 1, n, a);

for(int i = 1; i <= n; i++) a[i] = 1;

sz.build(sz.rt[0], 1, n, a);

}

}

int hs[N], c[N];

struct Edge{ int x, y, a; } e[M];

inline bool cmp(Edge A, Edge B){ return A.a < B.a; }

inline void solve(int l, int r){

Dsu::addban();

for(int i = l; i <= r; i++) hs[i] = Dsu::ban;

for(int i = l; i <= r; i++) Dsu::unite(e[i].x, e[i].y);

}

inline void BuildDsu(){

sort(e + 1, e + m + 1, cmp), e[0].a = e[m+1].a = -1;

Dsu::prepare();

for(int i = m, r; i >= 1; i--){

if(e[i].a != e[i+1].a) r = i;

if(e[i].a != e[i-1].a) solve(i, r);

}

hs[m+1] = 0;

for(int i = 1; i <= m; i++) c[i] = e[i].a;

}

inline int query(int u, int p){

int pos = upper_bound(c + 1, c + m + 1, p) - c;

int rt = Dsu::ask(hs[pos], u); return -fa.get(hs[pos], rt);

}

inline void cleartype(){

#define mem(x) memset(x, 0, sizeof(x))

fa.clear(), sz.clear();

mem(a), mem(b), mem(nxt), mem(head), cnt = 0;

}

inline void init(){

read(n), read(m);

for(int i = 1, x, y, z, a; i <= m; i++){

read(x), read(y), read(z), read(a);

add(x, y, z), add(y, x, z), e[i] = (Edge){x, y, a};

}

read(Q), read(K), read(S);

}

inline void realmain(){

cleartype(), init(), dijkstra(), BuildDsu();

int lastans = 0;

while(Q--){

int u, p; read(u), read(p);

p = (p + K * lastans) % (S + 1);

u = (u + K * lastans - 1) % n + 1;

printf("%d\n", lastans = query(u, p));

}

}

int main(){

int T; read(T); while(T--) realmain();

return 0;

}

「NOI2018」归程的更多相关文章

  1. loj#2718. 「NOI2018」归程

    题目链接 loj#2718. 「NOI2018」归程 题解 按照高度做克鲁斯卡尔重构树 那么对于询问倍增找到当前点能到达的高度最小可行点,该点的子树就是能到达的联通快,维护子树中到1节点的最短距离 s ...

  2. LOJ #2718. 「NOI2018」归程 Dijkstra+可持久化并查集

    把 $Noi2018$ day1t1 想出来还是挺开心的,虽然是一道水题~ 预处理出来 1 号点到其它点的最短路,然后预处理边权从大到小排序后加入前 $i$ 个边的并查集. 这个并查集用可持久化线段树 ...

  3. LOJ #2718. 「NOI2018」归程(Dijkstra + Kruskal重构树 + 倍增)

    题意 给你一个无向图,其中每条边有两个值 \(l, a\) 代表一条边的长度和海拔. 其中有 \(q\) 次询问(强制在线),每次询问给你两个参数 \(v, p\) ,表示在 \(v\) 出发,能开车 ...

  4. #2718. 「NOI2018」归程 kruskal重构树

    链接 https://loj.ac/problem/2718 思路 我们希望x所在的连通块尽量的大,而且尽量走高处 离线的话可以询问排序,kruskal过程中更新答案 在线就要用kruskal重构树 ...

  5. 洛谷 4768 LOJ 2718「NOI2018」归程

    [题解] 本题有多种做法,例如可持久化并查集.kruskal重构树等. kruskal重构树的做法是这样的:先把边按照海拔h从大到小的顺序排序,然后跑kruskal建立海拔的最大生成树,顺便建krus ...

  6. loj2718 「NOI2018」归程[Kruskal重构树+最短路]

    关于Kruskal重构树可以翻阅本人的最小生成树笔记. 这题明显裸的Kruskal重构树. 然后这题限制$\le p$的边不能走,实际上就是要满足走最小边权最大的瓶颈路,于是跑最大生成树,构建Krus ...

  7. 「NOI2018」屠龙勇士(EXCRT)

    「NOI2018」屠龙勇士(EXCRT) 终于把传说中 \(NOI2018D2\) 的签到题写掉了... 开始我还没读懂题目...而且这题细节巨麻烦...(可能对我而言) 首先我们要转换一下,每次的 ...

  8. LOJ #2721. 「NOI2018」屠龙勇士(set + exgcd)

    题意 LOJ #2721. 「NOI2018」屠龙勇士 题解 首先假设每条龙都可以打死,每次拿到的剑攻击力为 \(ATK\) . 这个需要支持每次插入一个数,查找比一个 \(\le\) 数最大的数(或 ...

  9. 「NOI2018」你的名字

    「NOI2018」你的名字 题目描述 小A 被选为了\(ION2018\) 的出题人,他精心准备了一道质量十分高的题目,且已经 把除了题目命名以外的工作都做好了. 由于\(ION\) 已经举办了很多届 ...

随机推荐

  1. 深入理解 Java 多线程核心知识:跳槽面试必备

    多线程相对于其他 Java 知识点来讲,有一定的学习门槛,并且了解起来比较费劲.在平时工作中如若使用不当会出现数据错乱.执行效率低(还不如单线程去运行)或者死锁程序挂掉等等问题,所以掌握了解多线程至关 ...

  2. 【洛谷 P4735】 最大异或和 (可持久化Trie)

    题目链接 维护整个数列的异或前缀和和\(s\),然后每次就是要求\(s[N]\text{^}x\text{^}s[k],l-1<=k<=r-1\)的最大值 如果没有\(l\)的限制,那么直 ...

  3. HDU 2553 N皇后问题 (深搜)

    题目链接 Problem Description 在N*N的方格棋盘放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成45角的斜线上. 你的任务是,对 ...

  4. 换行符 \r \n \r\n 在不同系统下的区别

    '\r'是回车,前者使光标到行首,(carriage return)'\n'是换行,后者使光标下移一格,(line feed)\r 是回车,return\n 是换行,newline对于换行这个动作,u ...

  5. 用例图(Use Case Diagram)

    用例图(Use Case Diagram) 执行者/参与者(Actor): 表示与您的应用程序或系统进行交互的用户.组织或外部系统.用一个小人表示. 用例(Use Case): 即系统具有的功能,在用 ...

  6. Python3 高阶函数

    高阶函数 (满足其一就是:(1)一个函数名作为另一个函数的形参:(2)返回值包含函数名;不修改函数的调用方式) 1.一个函数名作为另一个函数的形参 输出结果: 2.返回值包含函数名;不修改函数的 输出 ...

  7. 【bzoj4868】期末考试

    我还第一次见到省选考三分……? #include<bits/stdc++.h> #define N 200005 using namespace std; typedef long lon ...

  8. OC 01 类和对象

    一.  定义OC的类和创建OC的对象 接下来就在OC中模拟现实生活中的情况,创建一辆车出来.首先要有一个车子类,然后再利用车子类创建车子对象 要描述OC中的类稍微麻烦一点,分2大步骤:类的声明.类的实 ...

  9. Python抓取学院新闻报告

    Python案例 scrapy抓取学院新闻报告 任务 抓取四川大学公共管理学院官网(http://ggglxy.scu.edu.cn)所有的新闻咨询. 实验流程 1.确定抓取目标.2.制定抓取规则.3 ...

  10. MD5加密学习

    MD5(Message Digest --消息摘要算法)算法是一种散列(hash)算法(摘要算法,指纹算法),不是一种加密算法(易错),任何长度的任意内容都可以用MD5计算出散列值.主要作用是[验明“ ...