1. 程式人生 > 其它 >【CF1305G】Kuroni and Antihype(Boruvka+高維字首和)

【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;
}