@ysner
2018-08-17T18:20:41.000000Z
字数 1687
阅读 2734
最小生成树 脑洞
给定一个个节点的完全图,每个节点有个编号,节点和节点之间边的权值为,求该图的最小生成树的权值和。
有个新奇的最小生成树算法:
算法:
先对于每个点,选择在所有与之相连的边中,权值最小的边,并将这条边加入到最小生成树中。
显然这样连出来的边会形成一个森林,并且连边后连通块个数至少减半。
然后我们将每个连通块再看成一个点,重复以上算法即可。
时间复杂度。
从高位往低位贪心。
把所有点按当前位为还是分为两类。
显然这两类内部会形成联通块。
然后这两类间一定会有连边。
根据上面那个算法,如果我们选出两类数之间边权最小的那条边,这条边一定在最小生成树中。
因为这是当前状态下(两个大连通块)运行算法后将得到的结果。
接下来分治对两类数内部进行处理。
找边权最小值,直接树即可。
总复杂度。
#include<iostream>#include<cstdio>#include<cstdlib>#include<cstring>#include<cmath>#include<algorithm>#include<vector>#define re register#define il inline#define ll long long#define pb push_back#define max(a,b) ((a)>(b)?(a):(b))#define min(a,b) ((a)<(b)?(a):(b))#define fp(i,a,b) for(re int i=a;i<=b;i++)#define fq(i,a,b) for(re int i=a;i>=b;i--)using namespace std;const int mod=1e9+7,N=2e5+100;int n,tot,rt,t[N<<5][2];il ll gi(){re ll x=0,t=1;re char ch=getchar();while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();if(ch=='-') t=-1,ch=getchar();while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();return x*t;}il void Insert(re int &u,re int x,re int d){if(!u) u=++tot,t[u][0]=t[u][1]=0;if(d<0) return;Insert(t[u][(x>>d)&1],x,d-1);}il int Query(re int u,re int x,re int d){if(d<0) return 0;re int c=(x>>d)&1;if(t[u][c]) return Query(t[u][c],x,d-1);if(t[u][c^1]) return Query(t[u][c^1],x,d-1)^(1<<d);return 0;}il ll solve(vector<int>a,re int d){if(!a.size()||d<0) return 0;vector<int>b[2];re int mn=0;for(re int i=0;i<a.size();i++) b[(a[i]>>d)&1].pb(a[i]);if(b[0].size()&&b[1].size()){tot=rt=0;mn=1<<(d+1);for(re int i=0;i<b[0].size();i++) Insert(rt,b[0][i],30);for(re int i=0;i<b[1].size();i++) mn=min(mn,Query(rt,b[1][i],30));}return mn+solve(b[0],d-1)+solve(b[1],d-1);}int main(){n=gi();vector<int>a;fp(i,1,n) a.pb(gi());printf("%lld\n",solve(a,30));return 0;}
