複賽 1003 帶勁的and和(位運算,很好的題)
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;
}