Problem Description
度度熊专门研究过“动态传递闭包问题”,他有一万种让大家爆蛋的方法;但此刻,他只想出一道简简单单的题——至繁,归于至简。
度度熊有一张n个点m条边的无向图,第ii个点的点权为v_ivi。
如果图上存在一条路径使得点ii可以走到点jj,则称i,ji,j是带劲的,记f(i,j)=1f(i,j)=1;否则f(i,j)=0f(i,j)=0。显然有f(i,j) = f(j,i)f(i,j)=f(j,i)。
度度熊想知道求出: ![]()
其中\&&是C++中的and位运算符,如1&3=1, 2&3=2。
请将答案对10^9+7109+7取模后输出。
Input
第一行一个数,表示数据组数TT。
每组数据第一行两个整数n,mn,m;第二行nn个数表示v_ivi;接下来mm行,每行两个数u,vu,v,表示点uu和点vv之间有一条无向边。可能有重边或自环。
数据组数T=50,满足:
- 1 \le n,m \le 1000001≤n,m≤100000
- 1 \le v_i \le 10^91≤vi≤109。
其中90%的数据满足n,m \le 1000n,m≤1000。
Output
每组数据输出一行,每行仅包含一个数,表示带劲的and和。
Sample Input
Copy
1 5 5 3 9 4 8 9 2 1 1 3 2 1 1 2 5 2
Sample Output
99
题意:就是那个公式
思路:先判断两个点之间是不是连通,这是就想到了并查集,找出每个联通块,由给出的公式,模拟知,在一个连通块中,每一个数都要与连通块中其他的数,进行那个公式运算,刚开始,我暴力做的超时,当看到(vi&vj)时,你就应该想到位运算,还要有一个思想,就是不管是两个数相乘,还是求幂,都可以用位运算来解决
先把每个连通块中的数,从小到大排序,因为每个数都要与连通块中其他数进行 max(vi,vj) * (vi&vj)的运算,所以,排序,不影响,从小到大排序好处,先让前面小的进行位运算求和,再与当前这个大的相乘(因为当前这个与前面小的数相乘时,一定选的大 ,再乘于两个数相与,我们先让前面小数的进行位运算求和,在选出当前数二进制为1的位,进行相乘) 很好的做法 n*32的复杂度;
当一个集合中,任意一个数都 与集合中其他数,进行相乘一次仅一次且乘的是他们位运算相与的值(vi&vj)然后求和(如,每一个数,都与位置在它前面数进行相与,然后再相乘这个数,得出值求和)这种情况可以在 n*32的复杂度下求出,不用n*n/2的复杂度求。
看到式子中有位运算,你就应该想到,用位运算求解;
代码:
#include<bits/stdc++.h>
using namespace std;
#define Max 100005
#define ll long long
#define mod 1000000007
int a[Max];
int n,m;
int f[Max];
int s[70];
vector<int>v[Max]; // 把每个联通块放在一起;
int find(int x)
{
if(f[x] == x)
return x;
else return f[x] = find(f[x]);
}
int book[Max];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(int i = 1;i<=n;i++)
{
f[i] = i;
scanf("%d",&a[i]);
v[i].clear();
}
int x,y;
for(int i = 1;i<=m;i++)
{
scanf("%d%d",&x,&y);
int t1 = find(x);
int t2 = find(y);
if(t1!=t2)
{
if(t2>t1)
f[t2] = t1;
else f[t1] = t2;
}
}
memset(s,0,sizeof(s));
memset(book,0,sizeof(book));
for(int i = 1;i<=n;i++)
v[find(i)].push_back(a[i]);
ll ans = 0;
for(int i = 1;i<=n;i++)
{
int tt = find(i);
if(!book[tt])
{
book[tt] = 1;
if(v[tt].size()<=1) continue;
memset(s,0,sizeof(s));
sort(v[tt].begin(),v[tt].end());
for(int j = 0;j<v[tt].size();j++)
{
int kk = v[tt][j];
int temp = kk;
int p = 0;
while(kk) //变成位运算;
{
if(kk&1)
{
// 这个取模很重要;让我改错改了老长时间;
ans += ((ll)(temp)*(ll)s[p])%mod*(1LL<<p)%mod;
ans%=mod;
}
s[p++] += kk&1;
kk>>=1;
}
}
}
}
printf("%I64d\n",ans);
}
return 0;
}
本文探讨了一个基于图论的算法挑战,通过并查集和位运算技巧,解决了一个复杂的数学问题,即计算特定图中所有点对之间的带劲的and和。文章详细解释了如何使用并查集确定连通组件,并利用位运算优化计算过程。
649

被折叠的 条评论
为什么被折叠?



