[题解]决斗 异或最小生成树

Problem

]

Solution

可以发现,若将武士之间的决斗了的人连一条边,可以构成一颗树,边权为 a u ⨁ a v a_u \bigoplus a_v auav

同时要求边权最小,即最小生成树,但暴力跑出每条边是 n 2 n^2 n2的,所以考虑用 T r i e Trie Trie来优化。

首先有个贪心思想:若 a i = a j a_i = a_j ai=aj,则 i , j i,j i,j连边,接着按二进制位来贪心连边。

所以考虑建一颗 T r i e Trie Trie,从叶子节点开始合并联通块,对于 T r i e Trie Trie的某个节点的两个儿子所在的

联通块内找一条权值最小的边连接即可。

p.s.

mmp考试时sb一般地先开了一颗 T r i e Trie Trie,然后又动态开点合并 T r i e Trie Trie, R E + M L E RE+MLE RE+MLE,%$#@^#!@!!!

结果: 100 − > 50 100 -> 50 100>50

Code


/*******************************
Author:galaxy yr
LANG:C++
Created Time:2019年10月07日 星期一 09时01分15秒
*******************************/
#include<cstdio>
#include<algorithm>
#define int long long

using namespace std;

struct IO{
    template<typename T>
    IO & operator>>(T&res)
    {
        T q=1;char ch;
        while((ch=getchar())<'0' or ch>'9')if(ch=='-')q=-q;
        res=(ch^48);
        while((ch=getchar())>='0' and ch<='9') res=(res<<1)+(res<<3)+(ch^48);
        res*=q;
        return *this;
    }
}cin;

struct edge{
    signed to,next;
    int w;
    edge(int v=0,int nx=0,int f=0):to(v),next(nx),w(f){}
    bool operator<(const edge & p) const
    {
        return w<p.w;
    }
};

const signed maxn=1e5+10;
const int LG=20;
const int Lg=60;
const int inf=1e18;
const int MI=1ll<<Lg;
signed n,m,head[maxn],cnt,u,v,tot,depth[maxn],f[maxn][LG+1];
int a[maxn],ans,w,mx[maxn][LG+1];
edge e[maxn<<1];

void add(int u,int v,int w=0)
{
    e[++cnt]=edge(v,head[u],w);
    head[u]=cnt;
}

namespace Trie{

    signed ch[maxn*70][2],id[maxn*140],tot=1;

    void insert(int x,int _id)
    {
        int c,now=1;
        for(int i=Lg;i>=0;i--)
        {
            c=(x&(1ll<<i))>>i;
            if(!ch[now][c]) ch[now][c]=++tot;
            now=ch[now][c];
        }
        if(!id[now])
            id[now]=_id;
        else
        {
            add(_id,id[now]);
            add(id[now],_id);
        }
    }

    void query(int x,int y,int val,int mi)
    {
        if(val>w)return;
        if(w==0)return;
        if(!mi)
        {
            if(w>val)
            {
                w=val,u=id[x],v=id[y];
            }
            return;
        }
        if(ch[x][0] && ch[y][0])
            query(ch[x][0],ch[y][0],val,mi>>1);
        if(ch[x][1] && ch[y][1])
            query(ch[x][1],ch[y][1],val,mi>>1);
        if(ch[x][0] && ch[y][1])
            query(ch[x][0],ch[y][1],val|mi,mi>>1);
        if(ch[x][1] && ch[y][0])
            query(ch[x][1],ch[y][0],val|mi,mi>>1);
    }

    void solve(int now,int val,int mi)
    {
        if(!ch[now][0] && !ch[now][1])
            return;
        if(ch[now][0]) solve(ch[now][0],val,mi>>1);
        if(ch[now][1]) solve(ch[now][1],val|mi,mi>>1);
        w=inf;
        if(ch[now][0] && ch[now][1])
        {
            query(ch[now][0],ch[now][1],mi,mi>>1);
            add(u,v,w);
            add(v,u,w);
            ans+=w;
        }
    }

};

void dfs(int now)
{
    depth[now]=depth[f[now][0]]+1;
    for(int i=1;i<=LG;i++)
        f[now][i]=f[f[now][i-1]][i-1],mx[now][i]=max(mx[now][i-1],mx[f[now][i-1]][i-1]);
    for(int i=head[now];i;i=e[i].next)
        if(e[i].to!=f[now][0])
        {
            f[e[i].to][0]=now;
            mx[e[i].to][0]=e[i].w;
            dfs(e[i].to);
        }
}

int LCA(int x,int y)
{
    if(depth[x]<depth[y]) swap(x,y);
    int res=-1;
    for(int i=LG;i>=0;i--)
        if(depth[f[x][i]]>=depth[y])
            res=max(res,mx[x][i]),x=f[x][i];
    if(x==y) return res;
    for(int i=LG;i>=0;i--)
        if(f[x][i]!=f[y][i])
            res=max(res,max(mx[x][i],mx[y][i])),x=f[x][i],y=f[y][i];
    return max(res,max(mx[x][0],mx[y][0]));
}

signed main()
{
    //freopen("fight.in","r",stdin);
    //freopen("fight.out","w",stdout);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>a[i],Trie::insert(a[i],i);
    Trie::solve(1,0,MI);
    printf("%lld\n",ans);
    dfs(1);
    for(int i=1;i<=m;i++)
    {
        cin>>u>>v>>w;
        int res=(a[u]^a[v])&w;
        int tmp=LCA(u,v);
        printf("%lld\n",ans+min(res-tmp,0ll));
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值