P4768 [NOI2018]归程(Kruscal重构树)

这个算法是因为借用了kruscal合并并查集集合维护连通性,并且重建树不破坏结构得名。

具体的思想是:

我们按照边权排序(本题从大到小排序)

我们在不断合并集合的同时,每次合并两个集合的祖先,都建立一个虚拟原点,使得这个点指向两个祖先,并且边权等于点权。这就是kruscal重构树,本质上是一个二叉堆。

这样做的好处是,树上的点权都是符合单调性的,可以解决一类只能经过大于指定长度的边的图论问题。我们只需要倍增跳跃,满足条件的子树都是互通的。

对于这一题,先用最短路维护答案,建树后,dfs就能求得满足边不小于指定长度的集合的到终点的最小距离。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> pll;
const int N=1e6+10;
const int M=2e6+10;
const int inf=0x3f3f3f3f;
int n,m,Q,K,S;
int f[N][22],h[N],ne[N],e[N],idx;
int h1[N],ne1[N],e1[N],num;
ll last,d[N];
int p[N],depth[N],w[N],st[N];
struct node{
    ll a,b,c,d;//c:长度 d:海拔
}tr[N],ans[N];
void add1(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void add(int a,int b){
    e1[num]=b,ne1[num]=h1[a],h1[a]=num++;
}
void dij(){
    memset(d,0x3f,sizeof d);
    memset(st,0,sizeof st);
    d[1]=0;
    priority_queue<pll,vector<pll>,greater<pll>> q;
    q.push({d[1],1});
    while(q.size()){
        auto t=q.top();
        q.pop();
        if(st[t.second])
            continue;
        st[t.second]=1;
        for(int i=h[t.second];i!=-1;i=ne[i]){
            int j=e[i];
            if(d[j]>d[t.second]+w[i]){
                d[j]=d[t.second]+w[i];
                q.push({d[j],j});
            }
        }
    }

    for(int i=1;i<=n;i++)
         ans[i].c=d[i];
}
void init(){
    num=idx=last=0;
    memset(f,0,sizeof f);
    memset(h1,-1,sizeof h1);
    memset(h,-1,sizeof h);
}
int find(int x){
    if(p[x]!=x){
        p[x]=find(p[x]);
    }
    return p[x];
}
bool cmp(node a,node b){
    return a.d>b.d;
}
void dfs(int u,int pa){//求取krusacl重构树上的最小值,因为子树上的点可以互相到达
    depth[u]=depth[pa]+1;
    f[u][0]=pa;
    int i;
    for(i=1;i<=20;i++) f[u][i]=f[f[u][i-1]][i-1];
    for(i=h1[u];i!=-1;i=ne1[i]){
        int j=e1[i];
        dfs(j,u);
        ans[u].c=min(ans[u].c,ans[j].c);
    }
}
ll query(int x,int y){//倍增跳到最上面满足条件的点
    for(int i=20;i>=0;--i) if(depth[x]-(1<<i)>0&&ans[f[x][i]].d>y) x=f[x][i];
    return ans[x].c;
}
void solve(){
    int i;
    for(i=1;i<=2*n;i++)
        p[i]=i;
    sort(tr+1,tr+1+m,cmp);
    int tot=0,cnt=n;
    for(i=1;i<=m;i++){//建立虚拟点,并用点权代替边权
        int pa=find(tr[i].a);
        int pb=find(tr[i].b);
        if(pa!=pb){
            add(++cnt,pa);//因为并查集破坏结构因此重建树
            add(cnt,pb);
            p[pa]=cnt;
            p[pb]=cnt;
            ans[cnt].d=tr[i].d;
            tot++;
        }
        if(tot==n-1)
            break;
    }
    dfs(cnt,0);
    while(Q--){
        int u,v;
        cin>>u>>v;
        int x=(K*last+u-1)%n+1,y=(K*last+v)%(S+1);
        cout<<(last=query(x,y))<<endl;
    }
}
int main(){
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--){
        cin>>n>>m;
        init();
        int i;
        for(i=1;i<=m;i++){
            int a,b,c,d;
            cin>>a>>b>>c>>d;
            tr[i]={a,b,c,d};//kruscal存边
            add1(a,b,c);
            add1(b,a,c);
        }
        for(i=n+1;i<=n*2;i++)
            ans[i].c=0x3f3f3f3f;//初始点不存在的点先赋值为极大值
        dij();
        cin>>Q>>K>>S;
        solve();
    }
}
View Code

 

posted @ 2020-09-08 14:57  朝暮不思  阅读(185)  评论(0编辑  收藏  举报