2020牛客暑期多校训练营(第五场)B题Graph(01字典树)
2020牛客暑期多校训练营(第五场)B题Graph(01字典树)
题意:给你一个树与每个边的权值,你可以进行任意操作,加边或删边,加边的值为其连成环的其他边的异或,删边后要保证图连通,求所有边和的最小值。
题解:对于任意两点,都可以求出其边,要么题目给你了,要么可以通过异或算出来,即给了你一个完全图,然后求一个最小生成树既可。所有点间的边显然存不下,但是存在点上就可以用O(n)的内存用异或公式求出任意边,所以我们可以把边的权值转移为点上的权值,点的权值为其前缀异或;
如题目样例:
6
0 1 1
1 2 4
1 3 3
0 4 5
0 5 2
转移为点上权值后为:0 1 5 2 5 2
然后任意两点间的边为两点的异或值
之后就是求一个xor的最小生成树既可,这边推荐一个大佬的博客,写得很好
方法是在字典树上求最小生成树,将所有点权放进字典树,对于字典树每一层,其为0的一端必然要与1的一端连接(如果都有点),记录枚举为0一端的所有权值与为1一端的所有权值xor(n方超时)的最小答案,这里优化,将为0一端的所有权值进为1一端的字典树中,设第i次枚举的值为x,对于其二进制的每一位在字典树中尽量走与他相同的路既可最优,时间从n方降为nlogn,层数为30,即总复杂度为O(30 * n * logn),说的有点抽象,这一段看不太懂的话可以去看下那个大佬的博客,讲的很好。
#include<iostream>
#include<vector>
using namespace std;
#define ll long long
const ll N=200007;
ll maxn=1e18+7;
ll n,a[N],cnt,p,ans,u,v,c;
ll vis[N];
struct madoka{
ll l;
ll r;
}ma[30*N];
vector<ll>ho;
vector<pair<ll,ll> >us[N];
void go(ll p,ll c){
vis[p]=1;
a[p]=c;
for(int i=0;i<us[p].size();i++){
if(vis[us[p][i].first]==0){
go(us[p][i].first,c^us[p][i].second);
}
}
}
void up(ll &p,ll z,ll h){
if(!p){
p=++cnt;
}
if(h==-1)return;
if(z>=(1<<h)){
up(ma[p].r,z-(1<<h),h-1);
}
else{
up(ma[p].l,z,h-1);
}
}
ll qry(ll z,ll p,ll h){
if(h==-1)return 0;
int lin=z&(1<<h);
if(lin){
if(ma[p].r){
return qry(z,ma[p].r,h-1);
}
else{
return (1<<h)+qry(z,ma[p].l,h-1);
}
}
else{
if(ma[p].l){
return qry(z,ma[p].l,h-1);
}
else{
return (1<<h)+qry(z,ma[p].r,h-1);
}
}
}
ll dfs(vector<ll>ho,ll p,ll h){
if(p==0)return 0;
vector<ll>lp,rp;
ll ans=maxn;
ll lim=(1<<h);
for(int i=0;i<ho.size();i++){
if(ho[i]&lim){
rp.push_back(ho[i]);
}
else{
lp.push_back(ho[i]);
}
}
if(lp.size()&&rp.size()){
for(int i=0;i<rp.size();i++){
ans=min(ans,(1<<h)+(ll)qry(rp[i],ma[p].l,h-1));
}
}
else{
ans=0;
}
return ans+dfs(lp,ma[p].l,h-1)+dfs(rp,ma[p].r,h-1);
}
int main(){
scanf("%lld",&n);
for(int i=1;i<n;i++){
scanf("%lld%lld%lld",&u,&v,&c);
us[u].push_back({v,c});
us[v].push_back({u,c});
}
go(0,0);
for(int i=0;i<n;i++){
ho.push_back(a[i]);
up(p,a[i],30);
}
printf("%lld\n",dfs(ho,1,30));
}