1. 程式人生 > >複賽 1003 帶勁的and和(位運算,很好的題)

複賽 1003 帶勁的and和(位運算,很好的題)

Problem Description

度度熊專門研究過“動態傳遞閉包問題”,他有一萬種讓大家爆蛋的方法;但此刻,他只想出一道簡簡單單的題——至繁,歸於至簡。

度度熊有一張n個點m條邊的無向圖,第ii個點的點權為v_iv​i​​。

如果圖上存在一條路徑使得點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+710​9​​+7取模後輸出。

Input

第一行一個數,表示資料組數TT。

每組資料第一行兩個整數n,mn,m;第二行nn個數表示v_iv​i​​;接下來mm行,每行兩個數u,vu,v,表示點uu和點vv之間有一條無向邊。可能有重邊或自環。

資料組數T=50,滿足:

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

其中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;
}