luogu P4768 [NOI2018] 归程 - 最短路 - 并查集 - kruskal重构树

传送门:https://www.luogu.org/problem/show?pid=4768

题目大意:给定无向图,每条边有长度和高度,多次询问从某个点pi出发到1号点的最短路,并给出参数qi,规定所有能从pi出发仅通过高度>qi的边到达的点视为可以不耗费代价地到达。

考场上写了一堆部分分,在距离比赛结束还有7分钟的时候想出了正解然而没时间写了……80分滚粗嘤嘤嘤。

正解好像叫做“kruskal重构树”,不过本蒟蒻表示没听说过这个名词,那就尽量用通俗易懂的语言讲明白这是个什么东西吧。

如果不强制在线,有一种比较直观的做法:按高度降序排序后,每次加入一条边相当于合并两个连通块,询问相当于查询当前连通块中dis的最小值,显然可以直接用并查集维护。

然而这题要强制在线,咋办?可持久化并查集?

其实是可以的,因为 只有访问历史版本没有修改,可以通过一些操作使得复杂度控制在一个log,不过本蒟蒻并不会写……

(好像直接写普通的可持久化并查集再卡卡常也能过的样子)。

当然这东西比较烦谁都不想写,那么还可以怎样?利用“只有访问历史版本没有修改”的性质,我们可以设计如下算法:

在每次合并时,新建一个节点,将这两个连通块合并上去,同时记录当前节点的dis是两个集合的dis的min,高度是当前边的高度。

这样预处理完之后,整张图会被建成一棵树,原始节点是叶子,高度信息是从叶子到根递减的。

于是,对于一组询问,我们可以直接倍增找到对应的合并节点,然后直接查询dis即可。

复杂度O(nlogn)。

还有一个小插曲:求最短路时一定要写dij,至于spfa?很遗憾它死了。

#include<bits/stdc++.h>
using namespace std;
int t,n,m;
#define li long long
#define gc getchar()
#define pc putchar
int read(){
    int x = 0,c = gc;
    while(!isdigit(c)) c = gc;
    while(isdigit(c)){
        x = (x << 1) + (x << 3) + (c ^ '0');
        c = gc;
    }
    return x;
}
void print(int x){
    if(x >= 10) print(x / 10);
    pc(x % 10 + '0');
}
struct edge{
    int fr,to,nxt,val,hg;
}e[800010];
int ct,cnt,tc,fir[200010];
struct eg{
    int fr,to,hg;
}o[400010];
void ins(int u,int v,int w,int x){
    e[++cnt].fr = u;e[cnt].to = v;e[cnt].nxt = fir[u];fir[u] = cnt;e[cnt].val = w;e[cnt].hg = x;
    e[++cnt].fr = v;e[cnt].to = u;e[cnt].nxt = fir[v];fir[v] = cnt;e[cnt].val = w;e[cnt].hg = x;
    o[++ct].fr = u;o[ct].to = v;o[ct].hg = x;
}
bool operator < (eg q,eg w){
    return q.hg > w.hg;
}
int f[400010],vl[400010];
int fa[400010];
int st[20][400010],mn[400010];
li dis[200010];
const int inf = 2007654321;
int p,k,s;
li as; 
priority_queue<pair<int,int> > q;
#define mp make_pair
#define fi first
#define se second
void dij(){
    int i,j;
    for(i = 2;i <= n;++i) dis[i] = inf;
    dis[1] = 0;q.push(mp(0,1));
    while(!q.empty()){
        pair<int,int> t = q.top();q.pop();
        if(-t.fi != dis[t.se]) continue;
        for(i = fir[t.se];i;i = e[i].nxt){
            j = e[i].to;
            if(-t.fi + e[i].val < dis[j]){
                dis[j] = -t.fi + e[i].val;
                q.push(mp(-dis[j],j));
            }
        }
    }
} 
int getf(int q){
    return f[q] == q ? q : f[q] = getf(f[q]);
}
void mg(int u,int v,int w){
    int x = getf(u),y = getf(v);
    if(x == y) return;
    f[x] = f[y] = st[0][x] = st[0][y] = ++tc;
    mn[tc] = w;
    vl[tc] = min(vl[x],vl[y]);
}
int lgo[400010],dpt[400010];
void buildst(){
    register int i,j;
    for(i = 1;i <= lgo[tc];++i){
        for(j = 1;j <= tc;++j) st[i][j] = st[i - 1][st[i - 1][j]];
    }
}
void dfs(int q){
    if(dpt[q] || q == tc) return;
    dfs(st[0][q]);
    dpt[q] = dpt[st[0][q]] + 1;
}
int main(){
    int u,v,w,x;
    int i,j;
    for(i = 2;i <= 400000;++i) lgo[i] = lgo[i >> 1] + 1;
    t = read();
    while(t--){
        memset(fir,0,sizeof(fir));ct = cnt = as = 0;
        n = read();m = read();tc = n;
        for(i = 1;i <= m;++i){
            u = read();v = read();w = read();x = read();ins(u,v,w,x);
        }
        dij();
        sort(o + 1,o + ct + 1);
        for(i = 1;i <= n * 2;++i) f[i] = i,fa[i] = dpt[i] = 0;
        for(i = 1;i <= n;++i) vl[i] = dis[i];
        for(i = 1;i <= ct;++i) mg(o[i].fr,o[i].to,o[i].hg);
        buildst();
        for(i = 1;i <= tc;++i) if(!dpt[i]) dfs(i);
        p = read();k = read();s = read();
        int tot = 0; 
        for(i = 1;i <= p;++i){
            u = read();v = read();
            u = (u + k * as - 1) % n + 1;
            v = (v + k * as) % (s + 1);
            for(j = lgo[dpt[u]];j >= 0;--j) mn[st[j][u]] > v ? u = st[j][u] : 0;
            print(as = vl[u]);pc('\n');
        }
    }
    return 0;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值