[NOI2018]归程 - 最小生成树 - 最短路 - kruskal重构树

8 篇文章 0 订阅
4 篇文章 0 订阅

做法是这样的,考虑kruskal的过程,每次合并两个联通块就新建一个点连向这个点并且边权是合并的时刻,这样时刻从下到上是递增的,每次询问的时候二分即可求出一个点在某个时刻所在的联通块的信息。

// luogu-judger-enable-o2
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<utility>
#include<queue>
#include<climits>
#define mp make_pair
#define fir firse
#define sec second
#define gc getchar()
#define N 400010
#define M 400010
#define LOG 22
#define INF INT_MAX
using namespace std;
typedef pair<int,int> pii;
inline int inn()
{
    int x,ch;while((ch=gc)<'0'||ch>'9');
    x=ch^'0';while((ch=gc)>='0'&&ch<='9')
        x=(x<<1)+(x<<3)+(ch^'0');return x;
}
struct edges{
    int to,pre,wgt;
}e[M<<1];int h[N],d[N],etop;
priority_queue<pii> q;int vis[N];
inline int add_edge(int u,int v,int w)
{   return e[++etop].to=v,e[etop].pre=h[u],h[u]=etop,e[etop].wgt=w; }
struct Es{
    int u,v,a;
    inline bool operator<(const Es &e)const{return a>e.a;}
}es[M];int fa[N],dpt[N];
inline int dijkstra(int s,int n)
{
    while(!q.empty()) q.pop();
    for(int i=1;i<=n;i++) d[i]=INF;
    memset(vis,0,sizeof(int)*(n+1));
    d[s]=0,q.push(mp(0,s));
    while(!q.empty())
    {
        int x=q.top().sec;q.pop();
        if(vis[x]) continue;vis[x]=1;
        for(int i=h[x],y;i;i=e[i].pre)
            if(d[y=e[i].to]>d[x]+e[i].wgt)
                d[y]=d[x]+e[i].wgt,q.push(mp(-d[y],y));
    }
    return 0;
}
inline int findf(int x)
{
    int fx=x,y;while(fx^fa[fx]) fx=fa[fx];
    while(x^fx) y=fa[x],fa[x]=fx,x=y;return x;
}
int val[N][LOG],up[N][LOG];
inline int kruskal(int n,int m)
{
    sort(es+1,es+m+1);int c=n;
    for(int i=1;i<=2*n;i++) fa[i]=i;
    for(int i=1,ing=0;i<=m&&ing<n-1;i++)
    {
        int x=findf(es[i].u),y=findf(es[i].v),a=es[i].a;
        if(x^y) val[x][0]=val[y][0]=a,fa[x]=fa[y]=++c,
        d[up[x][0]=up[y][0]=c]=min(d[x],d[y]),ing++;
    }
    for(int i=0;i<LOG;i++) up[c][i]=0,val[c][i]=-1;
    for(int i=c;i>=1;i--) dpt[i]=dpt[up[i][0]]+1;
    for(int i=c-1;i>=1;i--) for(int j=1;j<LOG;j++)
        up[i][j]=up[up[i][j-1]][j-1],val[i][j]=min(val[i][j-1],val[up[i][j-1]][j-1]);
    return 0;
}
inline int getLCA(int x,int y)
{
    if(dpt[x]<dpt[y]) swap(x,y);
    for(int i=LOG-1;i>=0;i--)
        if(dpt[up[x][i]]>=dpt[y]) x=up[x][i];
    if(x==y) return x;
    for(int i=LOG-1;i>=0;i--)
        if(up[x][i]^up[y][i]) x=up[x][i],y=up[y][i];
    return up[x][0];
}
int main()
{
    for(int T=inn();T;T--)
    {
        int n=inn(),m=inn();
        memset(h,0,sizeof(int)*(n+1)),etop=0;
        for(int i=1,x,y,w;i<=m;i++)
            x=es[i].u=inn(),y=es[i].v=inn(),w=inn(),
            es[i].a=inn(),add_edge(x,y,w),add_edge(y,x,w);
        dijkstra(1,n),kruskal(n,m);
        for(int q=inn(),las=0,k=inn(),mxp=inn();q;q--)
        {
            int x=(inn()+k*las-1)%n+1,p=(inn()+k*las)%(mxp+1),y=x;
            for(int i=LOG-1;i>=0;i--) if(val[y][i]>p) y=up[y][i];
            printf("%d\n",las=d[y]*(dpt[getLCA(1,x)]<=dpt[y]));
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值