2018 百度之星复赛 C题 带劲的and和(并查集+安位贡献)

本文探讨了一种特殊的图论问题,即“动态传递闭包问题”。通过构造无向图并利用并查集算法,文章详细介绍了如何计算特定条件下点对间的最大权重和位运算结果,最终给出了一种高效的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

度度熊专门研究过“动态传递闭包问题”,他有一万种让大家爆蛋的方法;但此刻,他只想出一道简简单单的题——至繁,归于至简。

度度熊有一张n个点m条边的无向图,第iii个点的点权为viv_iv​i​​。

如果图上存在一条路径使得点iii可以走到点jjj,则称i,ji,ji,j是带劲的,记f(i,j)=1f(i,j)=1f(i,j)=1;否则f(i,j)=0f(i,j)=0f(i,j)=0。显然有f(i,j)=f(j,i)f(i,j) = f(j,i)f(i,j)=f(j,i)。

度度熊想知道求出: ∑i=1n−1∑j=i+1nf(i,j)×max(vi,vj)×(vi&vj)\sum_{i=1}^{n-1} \sum_{j=i+1}^{n} f(i,j) \times \max(v_i, v_j) \times (v_i \& v_j)∑​i=1​n−1​​∑​j=i+1​n​​f(i,j)×max(v​i​​,v​j​​)×(v​i​​&v​j​​)

其中&\&&是C++中的and位运算符,如1&3=1, 2&3=2。

请将答案对109+710^9+710​9​​+7取模后输出。

Input

第一行一个数,表示数据组数TTT。

每组数据第一行两个整数n,mn,mn,m;第二行nnn个数表示viv_iv​i​​;接下来mmm行,每行两个数u,vu,vu,v,表示点uuu和点vvv之间有一条无向边。可能有重边或自环。

数据组数T=50,满足:

  • 1≤n,m≤1000001 \le n,m \le 1000001≤n,m≤100000
  • 1≤vi≤1091 \le v_i \le 10^91≤v​i​​≤10​9​​。

其中90%的数据满足n,m≤1000n,m \le 1000n,m≤1000。

Output

每组数据输出一行,每行仅包含一个数,表示带劲的and和。

Sample Input

1

5 5

3 9 4 8 9

2 1

1 3

2 1

1 2

5 2

Sample Output

99

#pragma GCC optimize(2)
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
typedef long long ll;
int t, n, tot, pos, m;
int f[maxn];
int a[maxn];
vector<int>vec[maxn];
void init(int n)
{
	for (int i = 0; i <= n; i++)
	{
		f[i] = i;
	}
	return;
}
int find(int x)
{
	if (x == f[x])
	{
		return x;
	}
	else
	{
		return f[x] = find(f[x]);
	}
}
void merge(int u, int v)
{
	int x = find(u);
	int y = find(v);
	if (x == y)
	{
		return;
	}
	f[x] = y;
}
int main()
{
	//freopen("C://input.txt", "r", stdin);
	int t;
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; i++)
			scanf("%d", &a[i]), f[i] = i;
		for (int i = 1; i <= m; i++)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			merge(u, v);
		}
		for (int i = 1; i <= n; i++)
			vec[i].clear();
		for (int i = 1; i <= n; i++)
		{
			vec[find(i)].push_back(a[i]);
		}
		ll ans = 0;
		for (int i = 1; i <= n; i++)
		{
			sort(vec[i].begin(), vec[i].end());
			ll op[35] = { 0 };
			for (int j = 0; j < vec[i].size(); j++)
			{
				int pos = vec[i][j]; 
				for (int k = 0; k < 32 && pos; k++)   //安位拆分
				{
					if (pos & 1)
					{
						op[k]++;
					}
					pos >>= 1;
				}
			}
			for (int j = vec[i].size() - 1; j > 0; j--)
			{
				int pos = vec[i][j];
				for (int k = 0; k < 32 && pos; k++)
				{
					if (pos & 1)
					{
						op[k]--;
						ans = (ans + (1LL << k) % MOD * op[k] % MOD * vec[i][j] % MOD) % MOD;  
					}
					pos >>= 1;
				}
			}
		}
		printf("%I64d\n", ans);
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值