给定n(n<=2e5)个不同的数,第i个数ai(0<=ai<)
需要把n个数分成两个集合S1和S2
一个集合S的代价cost(S):
①若集合中只有一个数,代价为
②否则,为最小的,其中x、y是集合中的数,表示异或
最大化min(cost(S1),cost(S2)),并输出对应方案
即需要输出一个长为n的01串,0表示选的数在A集合,1表示选的数在B集合
乱搞AC & 官方题解
(i,j)连边的代价是a[i]^a[j],对n个点的完全图求最小生成树,
对最小生成树进行黑白染色即为方案,
第一条会引起最小生成树产生奇环,从而没被加入最小生成树的边权就是最大值
归纳法,考虑已经得到了最小生成树的一部分,
由于每次合并的时候,是用当前最小的代价合并两个连通分量,相当于边权增序
所以,在合并两个连通分量(u,v)时,设边权为w,
连边的意义,是使得u、v互斥在两个集合里
①如果(u,v)不在同一连通分量内,合并,就暂时避免了一次w出现在最终答案内,贪心
②如果(u,v)在同一连通分量内,考察在树上(u,v)之间的点的个数(包含端点)
(1)若为奇数,说明u和v已经在同一集合内,
再连w这条互斥边,会引起奇环,
为了维护二分图合法性,所以不能连,
第一次出现这种情况的w,就是最大化的最小值
(2)若为偶数,说明u和v已经在不同集合内,
再连w这条互斥边,也不会对答案造成影响,因为不会被统计在答案里
板子是20年打牛客多校的时候抄的,感觉boruvka只在诸如trie树上的最小生成树上比较常用
在trie树递归合并得到最小生成树,合并的时候取边是从两个叶子上取的
复杂度O(n*logn*logV)
#include
//#include
using namespace std;
#define pb push_back
#define fi first
#define se second
typedef long long ll;
typedef pair P;
const int N=2e5+10,M=30*N;
int n,u,v,w,a[N],tr[M][2],id[M],c;
char ans[N];
vectore[N];
bool vis[N];
void dfs(int u,int w){
ans[u]=w+'0';
vis[u]=1;
for(auto &v:e[u]){
if(vis[v])continue;
dfs(v,w^1);
}
}
void ins(int v,int y){
int rt=0;
for(int j=29;j>=0;--j){
int x=v>>j&1;
if(!tr[rt][x]){
tr[rt][x]=++c;
tr[c][0]=tr[c][1]=id[c]=0;
}
rt=tr[rt][x];
}
id[rt]=y;
}
P unite(int x,int y,int d){
if(d==0){
return P(a[id[x]]^a[id[y]],1ll*id[x]*N+id[y]);
}
P res=P(1<<30,0);
bool zero=0;
for(int i=0;i<2;++i){
if(tr[x][i] && tr[y][i]){
res=min(res,unite(tr[x][i],tr[y][i],d-1));
zero=1;
}
}
if(!zero){
if(tr[x][0] && tr[y][1]){
res=min(res,unite(tr[x][0],tr[y][1],d-1));
}
else if(tr[x][1] && tr[y][0]){
res=min(res,unite(tr[x][1],tr[y][0],d-1));
}
}
return res;
}
void cal(int u,int d){
int ls=tr[u][0],rs=tr[u][1];
if(ls)cal(ls,d-1);
if(rs)cal(rs,d-1);
if(ls && rs){
P x=unite(ls,rs,d-1);
int y=x.se/N,z=x.se%N;
e[y].pb(z);
e[z].pb(y);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
ins(a[i],i);
}
for(int i=1;i<=n;++i){
ins(a[i],i);
}
cal(0,30);
for(int i=1;i<=n;++i){
if(!vis[i])dfs(i,1);
}
ans[n+1]='\0';
printf("%s\n",ans+1);
return 0;
}
算是抄一个boruvka的正统写法的板子吧,不局限于trie树
#include
using namespace std;
#define pb push_back
#define mp make_pair
#define forn(i, n) for (int i = 0; i < (int)(n); ++i)
typedef long long LL;
typedef pair PII;
const int N = 200000;
const int NODES = 32 * N;
const int INF = int(2e9);
int n;
int a[N];
int nx[NODES][2], cnt[NODES], fn[NODES];
int nodeCount = 1;
void addInt(int x, int pos) {
int ind = 0;
for (int i = 29; i >= 0; --i) {
int bit = (x >> i) & 1;
if (nx[ind][bit] == 0) {
nx[ind][bit] = nodeCount++;
}
ind = nx[ind][bit];
++cnt[ind];
}
fn[ind] = pos;
}
void addInt(int x) {
int ind = 0;
for (int i = 29; i >= 0; --i) {
int bit = (x >> i) & 1;
ind = nx[ind][bit];
++cnt[ind];
}
}
void removeInt(int x) {
int ind = 0;
for (int i = 29; i >= 0; --i) {
int bit = (x >> i) & 1;
ind = nx[ind][bit];
--cnt[ind];
}
}
PII findXor(int x) {
int ind = 0, res = 0;
for (int i = 29; i >= 0; --i) {
int bit = (x >> i) & 1;
if (cnt[nx[ind][bit]]) {
ind = nx[ind][bit];
} else {
ind = nx[ind][bit ^ 1];
res |= 1 << i;
}
}
return mp(res, fn[ind]);
}
int par[200000], ra[200000];
void dsuInit() {
forn(i, n) par[i] = i, ra[i] = 1;
}
int dsuParent(int v) {
if (v == par[v]) return v;
return par[v] = dsuParent(par[v]);
}
int dsuMerge(int u, int v) {
u = dsuParent(u);
v = dsuParent(v);
if (u == v) return 0;
if (ra[u] < ra[v]) swap(u, v);
par[v] = u;
ra[u] += ra[v];
return 1;
}
vector v[200000];
vector > toMerge;
vector g[200000];
int color[200000];
void coloring(int x, int c){
if(color[x] != -1) return;
color[x] = c;
for(auto y : g[x]) coloring(y, c ^ 1);
}
int main() {
scanf("%d", &n);
forn(i, n) scanf("%d", a + i);
forn(i, n) addInt(a[i], i);
dsuInit();
for (int merges = 0; merges < n - 1; ) {
forn(i, n) v[i].clear();
forn(i, n) v[dsuParent(i)].pb(i);
toMerge.clear();
forn(i, n) if (!v[i].empty()) {
for (int x : v[i]) {
removeInt(a[x]);
}
pair, int> res = mp(mp(INF, INF), INF);
for (int x : v[i]) {
res = min(res, mp(findXor(a[x]), x));
}
toMerge.pb(mp(res.first.first, mp(res.second, res.first.second)));
for (int x : v[i]) {
addInt(a[x]);
}
}
for (auto p : toMerge) {
if (dsuMerge(p.second.first, p.second.second)) {
++merges;
g[p.second.first].pb(p.second.second);
g[p.second.second].pb(p.second.first);
}
}
}
forn(i, n) color[i] = -1;
coloring(0, 1);
forn(i, n) printf("%d", color[i]);
puts("");
return 0;
}
1. 最大化最小值,首先考虑二分,二分答案v,将(a[p]^a[i])<=v的值连边
表示(p,i)这个点对不能出现在同一个集合里,
如果连边后的图是一个二分图,就可以向大二分,否则向小二分
最终的方案,由于是二分图,所以黑白染色后输出即可
2. 连边时,沿着trie树往下走,对于当前值a[i]来说,
满足(a[p]^a[i])<=v,在trie树的同一个节点上,只能有一个值
反证法:如果有两个的话,它们两个自身异或也会<=v,构成三元环导致无解
于是,从高位到低位遍历,遍历到第j位时,
根据(v在第j位为0/1,a[i]在第j位为0/1),决定下一步往0/1走,才能不超过v
每一步走法都是唯一的,每一个点最多连logV条边
3. 需要特判n=2,也就是没有边的情况
复杂度O(n*logn*logV*α(n))
#include
//#include
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<vec[M],e[N];
bool vis[N];
void dfs(int u,int w){
ans[u]=w+'0';
vis[u]=1;
for(auto &v:e[u]){
if(vis[v])continue;
//printf("u:%d v:%d w:%d\n",u,v,w);
dfs(v,w^1);
}
}
void ins(int v,int i){
int rt=0;
for(int j=29;j>=0;--j){
int x=v>>j&1;
if(!tr[rt][x]){
tr[rt][x]=++c;
tr[c][0]=tr[c][1]=num[rt]=0;
}
rt=tr[rt][x];
num[rt]++;
if(num[rt]<=2)vec[rt].pb(i);
}
}
int find(int x){
return par[x]==x?x:par[x]=find(par[x]);
}
void add(int x,int y){
x=find(x),y=find(y);
if(x==y)return;
par[y]=x;
}
void XOR(int x,int y){
e[x].pb(y);
e[y].pb(x);
add(x,y+n);
add(x+n,y);
}
// 要把满足(a[p]^a[i])<=v的(p,i)都连互斥边
// (v>>j&1,a[i]>>j&1,a[p]>>j&1)
bool ck(int i,int v){
int rt=0;
for(int j=29;j>=0;--j){
int x=a[i]>>j&1,z=v>>j&1,nex=tr[rt][x],oth=tr[rt][x^1];
//printf("i:%d v:%d j:%d x:%d z:%d nex:%d oth:%d\n",i,v,j,x,z,nex,oth);
if(!z)rt=nex;//(0,x,x)
else{
//分3、2、1讨论,分当前在不在x这一条路径讨论
if(num[nex]>=3)return 0;//3个两两互斥肯定寄
int cnt=0,p=-1;
for(auto &w:vec[nex]){
if(w==i)continue;
cnt++;
if(cnt>=2)return 0;//3个两两互斥肯定寄
p=w;
}
if(cnt){
//printf("XOR v:%d i:%d p:%d\n",v,i,p);
XOR(i,p);
}
rt=oth;
}
if(!rt)break;
}
return 1;
}
bool ok(int x){
rep(i,1,2*n){
if(i<=n)e[i].clear();
par[i]=i;
}
rep(i,1,n){
if(!ck(i,x))return 0;
}
rep(i,1,n){
if(find(i)==find(i+n))return 0;
}
return 1;
}
void sol(){
if(n==2){
puts("10");
return;
}
int l=0,r=(1<<30)-1;
while(l<=r){
int mid=l+(r-l)/2;
if(ok(mid))l=mid+1;
else r=mid-1;
}
//printf("r:%d\n",r);
ok(r);//ans=l
rep(i,1,n){
if(!vis[i]){
dfs(i,1);
}
}
ans[n+1]='\0';
printf("%s\n",ans+1);
}
int main(){
sci(n);
rep(i,1,n){
sci(a[i]);
ins(a[i],i);
}
sol();
return 0;
}
//7421 6530