题目描述
题目大意
有n个点和m个约束,每个约束包含三个数u,v,w,表示a[u]^a[v]=w.
求满足m个约束 并且 和最小的序列的和。
题目分析
这 很 明 显 是 一 个 图 论 问 题 , 对 于 每 个 约 束 u − v − w , 我 们 可 以 在 u 和 v 之 间 连 一 条 权 值 为 w 的 边 。 这很明显是一个图论问题,对于每个约束u-v-w,我们可以在u和v之间连一条权值为w的边。 这很明显是一个图论问题,对于每个约束u−v−w,我们可以在u和v之间连一条权值为w的边。
因 为 题 目 对 于 约 束 并 没 有 太 多 的 限 制 。 因 此 这 个 图 是 可 能 存 在 环 和 不 连 通 的 。 因为题目对于约束并没有太多的限制。因此这个图是可能存在环和不连通的。 因为题目对于约束并没有太多的限制。因此这个图是可能存在环和不连通的。
首 先 , 对 于 图 的 不 连 通 , 我 们 可 以 分 别 处 理 每 个 连 通 块 。 并 将 每 一 块 的 权 值 进 行 累 加 。 首先,对于图的不连通,我们可以分别处理每个连通块。并将每一块的权值进行累加。 首先,对于图的不连通,我们可以分别处理每个连通块。并将每一块的权值进行累加。
当 一 个 连 通 块 中 存 在 环 的 时 候 , 我 们 可 以 算 出 环 上 所 有 边 的 权 值 异 或 和 , 如 果 环 上 的 异 或 和 不 为 0 , 则 无 合 法 解 , 当一个连通块中存在环的时候,我们可以算出环上所有边的权值异或和,如果环上的异或和不为0,则无合法解, 当一个连通块中存在环的时候,我们可以算出环上所有边的权值异或和,如果环上的异或和不为0,则无合法解, 输 出 − 1 。 因 为 环 上 所 有 边 的 异 或 和 是 把 环 上 所 有 点 都 用 了 两 遍 , 因 此 环 上 的 异 或 和 应 该 是 0 。 输出-1。因为环上所有边的异或和是把环上所有点都用了两遍,因此环上的异或和应该是0。 输出−1。因为环上所有边的异或和是把环上所有点都用了两遍,因此环上的异或和应该是0。
除
去
这
两
种
情
况
,
剩
下
的
就
只
有
链
了
。
最
后
我
们
来
看
一
下
如
何
处
理
链
。
除去这两种情况,剩下的就只有链了。最后我们来看一下如何处理链。
除去这两种情况,剩下的就只有链了。最后我们来看一下如何处理链。
假
设
一
个
链
上
的
点
权
为
(
a
1
,
a
2
,
a
3
,
…
,
a
n
)
假设一个链上的点权为(a1,a2,a3,…,an)
假设一个链上的点权为(a1,a2,a3,…,an)
a
1
∧
a
2
=
w
1
a_1∧a_2=w_1
a1∧a2=w1
a
2
∧
a
3
=
w
2
a_2∧a_3=w_2
a2∧a3=w2
…
…
……
……
a
n
−
1
∧
a
n
=
w
n
−
1
a_{n-1}∧a_n=w_{n-1}
an−1∧an=wn−1
我 们 对 其 做 一 个 转 换 : 我们对其做一个转换: 我们对其做一个转换:
a
1
∧
a
2
=
w
1
a_1∧a_2=w_1
a1∧a2=w1
a
1
∧
a
3
=
w
1
∧
w
2
a_1∧a_3=w_1∧w_2
a1∧a3=w1∧w2
a
1
∧
a
4
=
w
1
∧
w
2
∧
w
3
a_1∧a_4=w_1∧w_2∧w_3
a1∧a4=w1∧w2∧w3
…
…
……
……
a
1
∧
a
n
=
w
1
∧
w
2
∧
…
∧
w
n
−
1
a_1∧a_n=w_1∧w_2∧…∧w_{n-1}
a1∧an=w1∧w2∧…∧wn−1
设 s i 为 w 1 − i 的 异 或 前 缀 和 , 我 们 再 移 一 下 项 设s_i为w_{1-i}的异或前缀和,我们再移一下项 设si为w1−i的异或前缀和,我们再移一下项
a
2
=
s
1
∧
a
1
a_2=s_1∧a_1
a2=s1∧a1
a
3
=
s
2
∧
a
1
a_3=s_2∧a_1
a3=s2∧a1
…
…
……
……
a
n
=
s
n
−
1
∧
a
1
a_n=s_{n-1}∧a_1
an=sn−1∧a1
因
此
,
我
们
只
需
要
确
定
第
一
个
数
a
1
,
即
可
算
出
所
有
的
其
它
的
点
的
点
权
。
因此,我们只需要确定第一个数a_1,即可算出所有的其它的点的点权。
因此,我们只需要确定第一个数a1,即可算出所有的其它的点的点权。
如
果
我
们
枚
举
a
1
的
所
有
可
能
值
,
那
一
定
是
会
超
时
的
。
所
以
我
们
需
要
寻
找
其
它
方
法
。
如果我们枚举a_1的所有可能值,那一定是会超时的。所以我们需要寻找其它方法。
如果我们枚举a1的所有可能值,那一定是会超时的。所以我们需要寻找其它方法。
我
们
可
以
按
位
枚
举
每
一
个
s
i
,
并
记
录
每
一
位
1
的
数
量
c
n
t
[
j
]
。
我们可以按位枚举每一个s_i,并记录每一位1的数量cnt[j]。
我们可以按位枚举每一个si,并记录每一位1的数量cnt[j]。
对
于
每
一
位
j
,
1
的
个
数
是
c
n
t
[
j
]
,
一
条
链
上
的
点
数
为
n
。
对于每一位j,1的个数是cnt[j],一条链上的点数为n。
对于每一位j,1的个数是cnt[j],一条链上的点数为n。
如 果 c n t [ j ] > n / 2 , 说 明 有 一 半 以 上 的 s i 在 第 j 位 上 都 是 1 。 因 为 a i + 1 = s i ∧ a 1 , 所 以 只 要 让 a 1 在 第 j 位 如果cnt[j]>n/2,说明有一半以上的s_i在第j位上都是1。因为a_{i+1}=s_i∧a_1,所以只要让a_1在第j位 如果cnt[j]>n/2,说明有一半以上的si在第j位上都是1。因为ai+1=si∧a1,所以只要让a1在第j位 也 为 1 , 就 能 让 一 半 以 上 的 点 权 减 少 ( 1 < < j ) , 而 剩 下 的 点 权 增 加 ( 1 < < j ) , 对 于 总 和 来 说 是 减 小 了 。 也为1,就能让一半以上的点权减少(1<<j),而剩下的点权增加(1<<j),对于总和来说是减小了。 也为1,就能让一半以上的点权减少(1<<j),而剩下的点权增加(1<<j),对于总和来说是减小了。
用 这 种 方 式 就 能 在 O ( n l o g n ) 的 复 杂 度 内 , 求 出 一 条 链 的 和 最 小 值 。 用这种方式就能在O(nlogn)的复杂度内,求出一条链的和最小值。 用这种方式就能在O(nlogn)的复杂度内,求出一条链的和最小值。
代码如下
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <set>
#include <map>
#include <queue>
#include <vector>
#include <algorithm>
#include <iomanip>
#define LL long long
#define ULL unsigned long long
#define PII pair<int,int>
#define PLL pair<LL,LL>
#define PDD pair<double,double>
#define x first
#define y second
using namespace std;
const int N=1e5+5,mod=1e9+7;
vector<PII> h[N];
bool vis[N],flag=1; //flag=0表示存在非法环,无解
LL f[N],cnt[35]; //f[v]表示从根节点到v点这条链上的异或和
vector<int> a; //记录这条链上的点(除第一个节点外)
void dfs(int u)
{
vis[u]=true;
for(auto t:h[u])
{
int v=t.x,w=t.y;
if(vis[v]) //如果v被访问过,说明存在环
{
if((f[u]^w)!=f[v]) flag=0; //如果环上的异或和不为0,用flag记录
}
else {
f[v]=f[u]^w; //更新f[v]的值
a.push_back(v); //将v加入链中
dfs(v); //继续递归
}
}
}
LL get()
{
memset(cnt,0,sizeof cnt); //先清空cnt[]
int x=0; //表示链上的第一个节点的点权
for(int i=0;i<30;i++)
{
for(int u:a) //记录链上所有数第i位是否为1(f[u]其实和讲解里的s[]等价)
if((f[u]>>i)&1) cnt[i]++;
if(cnt[i]>a.size()/2) x+=(1<<i);
}
LL ans=x;
for(int u:a) ans+=f[u]^x; //记录链上的点权和
return ans;
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
int n,m;
cin>>n>>m;
while(m--) //建图
{
int u,v,w;
cin>>u>>v>>w;
h[u].push_back({v,w});
h[v].push_back({u,w});
}
LL ans=0;
for(int i=1;i<=n;i++) //遍历所有点
{
if(!vis[i]&&flag) //如果点i没被访问过,且不存在非法环
{
a.clear(); //清空数组
dfs(i); //dfs求f[]
ans+=get(); //计算当前链的最小和
}
}
if(flag) cout<<ans<<endl;
else cout<<-1<<endl;
return 0;
}