Description
n n n个点编号 0 0 0~ n − 1 n-1 n−1, x , y x,y x,y之间边的权值为 x ⊕ y x\oplus y x⊕y,其中 ⊕ \oplus ⊕为逻辑与操作,求该图的最小权匹配
Input
一个整数 n ( 1 ≤ n ≤ 5 ⋅ 1 0 5 ) n(1\le n\le 5\cdot 10^5) n(1≤n≤5⋅105)
Output
输出 n n n个整数 p i p_i pi表示 i i i与 p i p_i pi匹配
Sample Input
3
Sample Output
0 2 1
Solution1
从大往小找匹配,尽量把高位的 1 1 1与上 0 0 0,故把 n n n个数插入字典树后,每次找和当前位不同的数字即可,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
Code1
#include<cstdio>
#include<cstring>
using namespace std;
#define maxn 500005
int chd[maxn*18][2],nn,num[maxn*18];
void init()
{
nn=1;
memset(chd[0],-1,sizeof(chd[0]));
}
void insert(int x,int sta)
{
int p=0;
for(int i=17;i>=0;i--)
{
int t=(x>>i)&1;
if(chd[p][t]==-1)
{
chd[p][t]=nn;
memset(chd[nn],-1,sizeof(chd[nn]));
nn++;
}
p=chd[p][t];
num[p]+=sta;
}
}
int find(int x)
{
int p=0;
int ret=0;
for(int i=17;i>=0;i--)
{
int t=((x>>i)&1)^1;
if(num[chd[p][t]]==0)t^=1;
p=chd[p][t];
ret<<=1;
ret|=t;
}
return ret;
}
int ans[maxn],vis[maxn];
int main()
{
int n;
scanf("%d",&n);
init();
for(int i=0;i<n;i++)insert(i,1);
memset(vis,0,sizeof(vis));
for(int i=n-1;i>=0;i--)
if(!vis[i])
{
insert(i,-1);
int j=find(i);
vis[i]=vis[j]=1;
ans[i]=j,ans[j]=i;
insert(j,-1);
}
for(int i=0;i<n;i++)printf("%d%c",ans[i],i==n-1?'\n':' ');
return 0;
}
Solution2
若 n = 2 m n=2^m n=2m,那么把 i i i和 n − 1 − i n-1-i n−1−i匹配即可使得答案为 0 0 0
如果 n > 2 m n>2^m n>2m,注意到 2 m , 2 m + 1 , . . . , 2 m + n − 2 m − 1 2^m,2^m+1,...,2^m+n-2^m-1 2m,2m+1,...,2m+n−2m−1与 2 m − 1 , 2 m − 2 , . . . , 2 m − ( n − 2 m ) 2^m-1,2^m-2,...,2^m-(n-2^m) 2m−1,2m−2,...,2m−(n−2m)每一位二进制位都不同,故此时可以处理掉后 2 ⋅ ( n − 2 m ) 2\cdot (n-2^m) 2⋅(n−2m)个数字,之后递归处理即可,时间复杂度为 O ( n ) O(n) O(n)
Code2
#include<cstdio>
using namespace std;
#define maxn 500005
int n,ans[maxn];
void Solve(int n)
{
int m=1;
while((m<<1)<=n)m<<=1;
if(n==m)
{
for(int i=0;i<n;i++)ans[i]=n-1-i;
return ;
}
for(int i=m;i<n;i++)ans[i]=2*m-1-i,ans[2*m-1-i]=i;
n-=2*(n-m);
Solve(n);
}
int main()
{
scanf("%d",&n);
Solve(n);
for(int i=0;i<n;i++)printf("%d%c",ans[i],i==n-1?'\n':' ');
return 0;
}