Educational Codeforces Round 152 F. XOR Partition(boruvka完全图最小生成树/二分+trie+贪心+二分图判定)

题目

给定n(n<=2e5)个不同的数,第i个数ai(0<=ai<2^{30})

需要把n个数分成两个集合S1和S2

一个集合S的代价cost(S):

①若集合中只有一个数,代价为2^{30}

②否则,为最小的x\bigoplus y,其中x、y是集合中的数,\bigoplus表示异或

最大化min(cost(S1),cost(S2)),并输出对应方案

即需要输出一个长为n的01串,0表示选的数在A集合,1表示选的数在B集合

思路来源

乱搞AC & 官方题解

题解1(完全图最小异或生成树)

结论

(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)

代码1(trie树上boruvka,牛客多校板子)

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

代码2(标准boruvka,官方题解)

算是抄一个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;
}

题解2(二分+二分图判定)

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))

代码3(二分+trie+贪心+二分图判定,我的乱搞)

#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

你可能感兴趣的:(乱搞AC,#,#,字典树,二分,最小生成树,二分图判定,boruvka,trie字典树)