度度熊专门研究过“动态传递闭包问题”,他有一万种让大家爆蛋的方法;但此刻,他只想出一道简简单单的题——至繁,归于至简。
度度熊有一张n个点m条边的**无向图**,第ii个点的点权为vivi。
如果图上存在一条**路径**使得点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)。
度度熊想知道求出:
∑n−1i=1∑nj=i+1f(i,j)×max(vi,vj)×(vi&vj)∑i=1n−1∑j=i+1nf(i,j)×max(vi,vj)×(vi&vj)
其中&&是C++中的and位运算符,如1&3=1, 2&3=2。
请将答案对109+7109+7取模后输出。
Input
第一行一个数,表示数据组数TT。
每组数据第一行两个整数n,mn,m;第二行nn个数表示vivi;接下来mm行,每行两个数u,vu,v,表示点uu和点vv之间有一条无向边。**可能有重边或自环。**
数据组数T=50,满足:
- 1≤n,m≤1000001≤n,m≤100000
- 1≤vi≤1091≤vi≤109。
其中90%的数据满足n,m≤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
题意:中文题嘛....其实就是找相互连通的顶点 的公式和(两者之间的最大值 * 两者的 & 运算值 求和)。
思路:很容易想到用并查集找到相互连通的顶点。之后就是在集合里运算了...直接暴力求和?结果当然是TLE了。
那么我们来换一种思路:在这里,我们是要求 两者之间的最大值 * 两者的 & 运算值 求和;对于最大值,我们可以从小到大排一下序,那么此时我们求的就是:(好丑...)
接下来我们再看十进制乘二进制 乘法运算emmmm....Orz....
接下来我们再看 & 运算emmmm....Orz....
好了,重点来了!!!!因为我们先对集合内的元素进行排序,公式可以转化成图一那样:也就是说对于第n个数,它要乘以
(1--> n-1 中每一数和它 & 的和)。那么我们就可以开一个dp数组记录,到达第n个数之前,每一位(二进制)出现的次数,因为 &运算中(两个都1 结果才为 1),如果第n个数的第 k 位(二进制)为 1 的话,我们就需要看看前面的那些数中有没有第k位为1的数,假设有dp[k]个,那么这个数(Xn)就可以利用十进制乘二进制求和了: (Xn * dp[k] * (1<<k)) 然后将 dp[k] ++ 继续运算就好了。
代码如下:
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define N 100010
#define ll long long
using namespace std;
const int mod = 1e9+7;
int n,f[N],a[N];
vector<int>v[N];
void init()
{
for(int i=1; i<=n; i++)
{
f[i]=i;
v[i].clear();
}
}
int getf(int x)
{
if(x==f[x])
return x;
return f[x]=getf(f[x]);
}
void merge(int x,int y)
{
int t1=getf(x);
int t2=getf(y);
if(t1!=t2)
f[t2]=t1;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int m;
scanf("%d%d",&n,&m);
init(); //初始化
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
for(int i=1; i<=m; i++)
{
int x,y;
scanf("%d%d",&x,&y);
merge(x,y); //并查集
}
for(int i=1; i<=n; i++)
{
int x=getf(i);
v[x].push_back(a[i]); //所有连通点放在一块
}
ll sum=0,dp[50];
for(int i=1; i<=n; i++)
{
if(v[i].size()>1) //这个集合个数 > 1
{
int l=v[i].size();
sort(v[i].begin(),v[i].end()); //排序
memset(dp,0,sizeof(dp)); //把二进制每一位初始化为 0
for(int j=0; j<l; j++) //枚举这个集合的所有数
{
int x=v[i][j];
for(int k=0; k<32&&x>=(1<<k); k++) //int型32为,
{
if(x&(1<<k)) //二进制 第k位(从后数)为 1
{
// & 运算 (1&1=1),x的第k位为1 在看以前统计的第k位的个数了,然后用十进制乘二进制分开运算
sum=(sum + x*dp[k]%mod*(1<<k)%mod )%mod; //十进制乘二进制,相当于把以前的那一堆运算给分开了
dp[k]++; //k位个数++ 为后面的数服务
dp[k]%=mod;
}
}
}
}
}
printf("%lld\n",sum);
}
}