• 【CF1305G】Kuroni and Antihype(Boruvka+高维前缀和)


    题目链接

    • 给定 \(n\) 个非负整数 \(a_{1\sim n}\)
    • 有一个初始为空的可重集合 \(S\)。每次选择一个尚未加入过 \(S\) 的元素 \(a_i\) 加入 \(S\),且可以选择 \(S\) 中一个与 \(a_i\) 按位与为 \(0\) 的元素 \(a_j\) 获得 \(a_j\) 的收益。
    • 求能获得的最大收益。
    • \(1\le n\le2\times10^5\)\(0\le a_i\le2\times10^5\)

    最大生成树

    首先可以新建一个权值为 \(0\) 的辅助点代表集合 \(S\)。则容易发现要求的其实就是最大内向树形图,其中一条边需要满足两端点权值按位与为 \(0\),且其权值为指向点的权值。

    最大树形图看起来比较麻烦,考虑到这里的边权比较特殊,尝试做一个转化。

    如果把边权看作两端点权值之和减去指出点的权值,因为每个点会恰好作为一次指出点,这部分贡献可以提出来最后减掉,那么边权就可以记作 两端点权值之和

    这样一来无论哪个方向边权都是一样的,就可以当作一张无向图来做了。

    然后便转化成了求最大生成树。

    Boruvka+高维前缀和

    这种问题应该非常容易就能想到 Boruvka 吧。

    那么关键就在于如何求出与一个点按位与为 \(0\) 且颜色不同的最大点权。

    \(x\)\(y\) 按位与为 \(0\),等价于 \(y\)\(x\) 补集的子集。

    所以考虑做一个高维前缀和,维护最大值以及与最大值颜色不同的一个次大值,就做完了。

    代码:\(O(V\log V\log n)\)

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Rg register
    #define RI Rg int
    #define Cn const
    #define CI Cn int&
    #define I inline
    #define W while
    #define N 2000000
    #define V 262144
    #define LG 17
    using namespace std;
    namespace FastIO
    {
    	#define FS 100000
    	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
    	char oc,FI[FS],*FA=FI,*FB=FI;
    	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
    	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    }using namespace FastIO;
    int n,a[N+5];
    struct S
    {
    	int mx,mp,sx,sp;S(RI a=0,RI b=0,RI c=0,RI d=0):mx(a),mp(b),sx(c),sp(d){}//维护最大值次大值以及相应颜色
    	I S operator + (Cn S& o) Cn//合并
    	{
    		if(mx==o.mx) {if(mp^o.mp) return S(mx,mp,o.mx,o.mp);return sx>=o.sx?S(mx,mp,sx,sp):S(mx,mp,o.sx,o.sp);}
    		if(mx>o.mx) {RI x,y;o.mp^mp?(x=o.mx,y=o.mp):(x=o.sx,y=o.sp);return sx>=x?S(mx,mp,sx,sp):S(mx,mp,x,y);}
    		RI x,y;mp^o.mp?(x=mx,y=mp):(x=sx,y=sp);return o.sx>=x?S(o.mx,o.mp,o.sx,o.sp):S(o.mx,o.mp,x,y);
    	}
    }s[V*10];
    int f[N+5];I int fa(CI x) {return f[x]?f[x]=fa(f[x]):x;}
    int ct,c[N+5],v[N+5],to[N+5],p[N+5];long long ans;void B()//Boruvka
    {
    	RI i,j;for(i=0;i^V;++i) s[i]=S(-1,-1,-1,-1);for(i=1;i<=ct;++i) v[i]=p[i]=-1;//清空
    	for(i=1;i<=n;++i) s[a[i]]=s[a[i]]+S(a[i],c[i],-1,-1),f[i]=0;
    	for(j=0;j<=LG;++j) for(i=0;i^V;++i) i>>j&1&&(s[i]=s[i]+s[i^(1<<j)],0);//高维前缀和
    	RI o,x,y;for(i=1;i<=n;v[c[i]]<x&&(v[c[i]]=x,to[c[i]]=y),++i)
    		s[o=a[i]^(V-1)].mp^c[i]?(x=~s[o].mx?a[i]+s[o].mx:-1,y=s[o].mp):(x=~s[o].sx?a[i]+s[o].sx:-1,y=s[o].sp);//补集子集中颜色不同的最大权值
    	for(i=1;i<=ct;++i) (x=fa(i))^(y=fa(to[i]))&&(f[x]=y),ans+=v[i];//连边
    	for(i=1;i<=ct;++i) p[fa(i)]=max(p[fa(i)],v[i]);for(o=ct,ct=0,i=1;i<=o;++i) !f[i]&&(ans-=p[i],p[i]=++ct);//每个连通块中最大边权重复计算要减去
    	if(ct==1) return;for(i=1;i<=n;++i) c[i]=p[fa(c[i])];B();//重新标号,继续做
    }
    int main()
    {
    	RI i;for(read(n),i=1;i<=n;++i) read(a[i]),ans-=a[i];
    	for(ct=++n,i=1;i<=n;++i) c[i]=i;return B(),printf("%lld\n",ans),0;
    }
    
  • 相关阅读:
    一个涉及到浮点寄存器的CM
    树和二叉树一篇就搞定!
    串的两种模式匹配方式(BF/KMP算法)
    队列的知识讲解与基本实现(数据结构)
    如何用C++实现栈
    判断List集合为空还是null的正确打开方式
    双链表的基本实现与讲解(C++描述)
    Redis从认识安装到实现增删改查
    如何使用C++实现单链表
    线性表——顺序表的实现与讲解(C++描述)
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF1305G.html
Copyright © 2020-2023  润新知