带劲的and和 (并查集找联通 + 二进制运算)

14 篇文章 0 订阅
12 篇文章 0 订阅

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

度度熊有一张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);
    }
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值