克鲁斯卡尔重构树

登录—专业IT笔试面试备考平台_牛客网

牛客遇到一道题目,1e7组询问,查询最小生成树上某两个点路径上边权最大值。

貌似是一道克鲁斯卡尔重构树的模板问题,依照克鲁斯卡尔求最小生成树的做法,把边按权值从小到大排序,枚举(x,y,val)加边时,不直接加边,先访问到x,y当前并查集内的根fx,fy,然后新开一个点np,把fx和fy都挂到np下面当儿子,np点的权值则是该边边权val。

性质,这样建出来的树两点间lca的权值就是原最小生成树两点间路径边权最大值。

因此问题变成了O(1)lca,经典的求欧拉序加st表维护即可。

#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define sc second
#define pb push_back
#define ll long long
#define trav(v,x) for(auto v:x)
#define all(x) (x).begin(), (x).end()
#define VI vector<int>
#define VLL vector<ll>
using namespace std;
const int N = 2e6 + 100;
const ll mod = 1e9 + 7;

struct E{
    int x, y, val;
    void in()
    {
        cin >> x >> y >> val;
    }
}edge[N];

int n, m, ff[N];

int find(int x)
{return ff[x] == x ? x : ff[x] = find(ff[x]);}

bool cmp(E x, E y)
{return x.val < y.val;}

vector<int> adj[N];

int  dep[N], val[N], ol[N], fst[N], st[N][20];
int tim = 0;

void dfs(int x, int fa)
{
    ol[++tim] = x;
    fst[x] = tim;
    trav(v, adj[x])
    {
        if(v == fa)
            continue;
        dep[v] = dep[x] + 1;
        dfs(v, x);
        ol[++tim] = x;
    }
}

typedef unsigned long long ull;
ull myRand(ull &k1, ull &k2){
    ull k3 = k1, k4 = k2;
    k1 = k4;
    k3 ^= (k3 <<23);
    k2 = k3 ^ k4 ^ (k3 >>17) ^ (k4 >>26);
    return k2 + k4;
}
pair<int,int>myRanq(ull&k1,ull&k2,int MAXN){
    int x=myRand(k1,k2)%MAXN+1,y=myRand(k1,k2)%MAXN+1;
    if(x>y)return make_pair(y,x);
    else return make_pair(x,y);
}


int lgx[N * 3];

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= m; i++)
    {
        edge[i].in();
    }
    sort(edge + 1, edge + m + 1, cmp);
    for(int i = 1; i <= n + n; i++)
        ff[i] = i, val[i] = 0;
    int llim = n + n - 1, nn;
    nn = n;
    for(int i = 1; i <= m; i++)
    {
        int x = edge[i].x;
        int y = edge[i].y;
        int fx = find(x);
        int fy = find(y);
        if(fx == fy)
            continue;
        ++n;
        ff[fx] = n;
        ff[fy] = n;
        adj[n].pb(fx);
        adj[n].pb(fy);
        val[n] = edge[i].val;
        if(n == llim)break;
    }
    int rt = find(1);
    dep[rt] = 1;
    dfs(rt, 0);
    for(int i = 1; i <= tim; i++)
        st[i][0] = ol[i];
    for(int lg = 1; lg < 20; lg++)
    {
        int len = (1 << lg);
        int md = (len >> 1);
        for(int i = 1; i + len - 1 <= tim; i++)
        {
            int nw = st[i][lg - 1];
            int nx = i + md;
            if(dep[st[nx][lg - 1]] < dep[nw])
                nw = st[nx][lg - 1];
            st[i][lg] = nw;
        }
    }
    lgx[1] = 0;
    for(int i = 2; i <= tim; i++)
        lgx[i] = lgx[i >> 1] + 1;
    int qq;
    cin >> qq;
    ull a1, a2;
    cin >> a1 >> a2;
    int res = 0;
    for(int i = 1; i <= qq; i++)
    {
        pii qry = myRanq(a1, a2, nn);
        int x = qry.fi;
        int y = qry.sc;
        x = fst[x], y= fst[y];
        if(x > y)
            swap(x, y);
        int k = lgx[y - x + 1];
        int ans = st[x][k];
        int tmp = st[y - (1 << k) + 1][k];
        if(dep[tmp] < dep[ans])
            ans = tmp;
        res ^= val[ans];
    }
    cout << res << '\n';
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值