题解:P14994 异或最短路和
EastSnowLotus · · 题解
首先我们来分析一下异或最短路的性质。如果做过类似题目的可以快进到底下。
假设我们初始有一条
于是不难证明,只要我们初始找任意一条
注意到后者问题的形式十分经典,我们可以考虑建出其异或线性基,即对于每一个简单环求出其权值然后全都放进线性基里。不过简单环的数量可能非常多。
根据某个神秘经典结论,随便取出一棵生成树之后,对于每一条非树边
于是我们也顺便解决了第一个问题。给生成树随便取一个根,进行一遍 dfs 即可求出每个点
于是我们的问题变为这样:
给定
n 个整数d_i 以及一个线性基F ,记x 选取一个F 的子集并异或上去能得到的最小权值为F(x) ,求\sum_i\sum_j F(d_i\oplus d_j) 。
首先我们能发现一个很关键的性质:
这个看上去并不是那么显然,为什么是对的呢?
可能存在比较严谨的线性代数证明,但是我线性代数很菜,这里给一个比较直观的理解方法:
::::info[证明]
考虑我们代码中建出线性基之后查询一个值
不妨假设
这样的话我们可以把过程化简为
| 那么这个时候就容易得到 |
|---|
至此,要解决的就是如下问题了:
给定
n 个整数a_i ,求\sum_i\sum_j(a_i\oplus a_j) 。
直接对于每一位考虑贡献,假设有
不过注意原图可能不连通,我们需要对每个连通块选一个根并求出所有的
这道题就解决了,总复杂度
struct three{int x,y,z;};
int n,m;
vector<three>G,V;
vector<pair<int,ll>>v[N];
int f[N];
int zx(int x){return x==f[x]?x:f[x]=zx(f[x]);}
void gt(){// 随便求出一个生成森林
for(auto[x,y,z]:G){
if(zx(x)==zx(y)){
V.push_back({x,y,z});
continue;
}
f[f[x]]=f[y];
v[x].push_back({y,z});
v[y].push_back({x,z});
}
}
ll d[N];
int frmc[N],cid;
void dfs(int now,int fa){// 计算 d
frmc[now]=cid;
for(auto[i,va]:v[now])if(i^fa)
d[i]=d[now]^va,dfs(i,now);
}
struct xxj{// 线性基
ll p[65];
void ins(ll x){
for(int i=63;~i;i--)if(x>>i&1){
if(!p[i]){p[i]=x;break;}
x^=p[i];
}
}
ll qry(ll x){
for(int i=63;~i;i--)if(x>>i&1)x^=p[i];
return x;
}
}bs[N];
vector<ll>vas[N];
ll cnt[N];
ll solve(vector<ll>a){// 计算 sum sum (a_i xor a_j)
int l=a.size();
for(int i=0;i<64;i++)cnt[i]=0;
for(auto v:a)for(int i=0;i<64;i++)cnt[i]+=(v>>i&1);
ll ans=0;
for(int i=0;i<64;i++)(ans+=(1ll<<i)%mod*cnt[i]%mod*(l-cnt[i])*2)%=mod;
return ans;
}
void __INIT__(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);}
void __SOLVE__(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y,z;
cin>>x>>y>>z;
G.push_back({x,y,z});
}
for(int i=1;i<=n;i++)f[i]=i;
gt();
for(int i=1;i<=n;i++)if(!frmc[i])++cid,dfs(i,i);
for(auto[x,y,z]:V)bs[frmc[x]].ins(d[x]^d[y]^z);
for(int i=1;i<=n;i++)vas[frmc[i]].push_back(bs[frmc[i]].qry(d[i]));
ll ans=0;
for(int i=1;i<=cid;i++)(ans+=solve(vas[i]))%=mod;
cout<<ans<<"\n";
}