HDU 6411(并查集+位运算)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6411

 

题目大意:建一个图,然后算出可以互相到达的点的最大值乘以二者位运算的结果的和。

 

题目思路:先通过并查集把可以互相到达的点放在一起。然后对每个并查集内部进行排序,因为是最大值乘以二者位运算结果的和,所以排序后就是后面的值乘以与前面所有值的位运算结果的和。然后就是对位运算的理解。举个例子。比如11*(11&7)可以看作11*((1+2+8)&(1+2+4))与是同一位都为1才为1,那么8和4都是独有的就变成0了,原式就变成了11*(1+2)。由此我们可以把前面所有的每一位有几个都算出来,有几个就是要用1<<k乘以几

 

以下是代码:

#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define MAXN 100005
#define inf 0x3f3f3f3f
#define ll long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
const int MOD=1e9+7;
vector<int>v[MAXN];
int pre[MAXN],a[MAXN],e[32];
int Find(int x){return pre[x]==x?x:pre[x]=Find(pre[x]);
}
int main()
{int t,n,m,x,y;scanf("%d",&t);while(t--){scanf("%d%d",&n,&m);rep(i,1,n)scanf("%d",&a[i]),pre[i]=i,v[i].clear();while(m--){scanf("%d%d",&x,&y);int xx=Find(x),yy=Find(y);if(xx!=yy){pre[xx]=yy;}}rep(i,1,n){v[Find(i)].push_back(a[i]);}ll ans=0;rep(i,1,n){sort(v[i].begin(),v[i].end());memset(e,0,sizeof(e));int len=v[i].size();rep(j,0,len-1){int temp=v[i][j];rep(k,0,32){if(temp==0)break;if(temp&1)e[k]++;temp>>=1;}}per(j,len-1,0){int temp=v[i][j];rep(k,0,32){if(temp==0)break;if(temp&1){e[k]--;ans=(ans+(((v[i][j]*(1ll<<k))%MOD)*(ll)e[k])%MOD)%MOD;;}temp>>=1;}}}ans=(ans+MOD)%MOD;printf("%lld\n",ans);}return 0;
}