度度熊专门研究过“动态传递闭包问题”,他有一万种让大家爆蛋的方法;但此刻,他只想出一道简简单单的题——至繁,归于至简
度度熊有一张n个点m条边的无向图,第i个点的点权为
。
如果图上存在一条路径使得点i可以走到点j,则称i,j是带劲的,记f(i,j)=1;否则f(i,j)=0。显然有f(i,j)=f(j,i)。
度度熊想知道求出:
其中&是C++中的and位运算符,如1&3=1, 2&3=2。
请将答案对
取模后输出。
第一行一个数,表示数据组数T。
每组数据第一行两个整数n,m;第二行n个数表示vi;接下来m行,每行两个数u,v,表示点u和点v之间有一条无向边。可能有重边或自环。
数据组数T=50,满足:
- 1≤n,m≤100000
- 1≤vi≤109。
其中90%的数据满足n,m≤1000。
每组数据输出一行,每行仅包含一个数,表示带劲的and和。
Input
1
5 5
3 9 4 8 9
2 1
1 3
2 1
1 2
5 2
OutPut
99
分析:这个很奇怪的公式,一看就知道要对其化简,不然太难解了,首先第一个
这个好解决,我们可以并查集维护在同一个联通快内的所有点,再看第二个
,这个的话,我们可以将每个联通快内的所有点排个序,贡献的传递关系就很明确了,最难的是第三个公式v_i & v_j ,真想不到怎么处理不同数&之间没有任何关系,这个怎么优化? 好吧,看了题解感觉太巧妙了。我么可以利用二进制拆位,拿一个例子来说:
a * b, 假设b的二进制位11011,那么这个乘积也可以转化为a * (1 << 4) + a * (1 << 3) + a * 0 + a * (1 << 1) + a * ( 1 << 0)
把这个思路用到这个题目上,对于同一联通块内的所有点,我们排好序(由小到大)之后,每个值我们都进行拆位,看当前位会对后面比它大的数的贡献。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD = (int) 1e9 + 7;
const int N = (int) 1e5 + 11;
int pre[N], val[N]; vector<int>ve[N];
int Find(int x){ return x == pre[x] ? x : (pre[x] = Find(pre[x])); }
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int T; scanf("%d", &T);
while(T--){
int n, m; scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++){
ve[i].clear(); pre[i] = i;
}
for(int i = 1; i <= n; i++) scanf("%d", &val[i]);
while(m--){
static int a, b; scanf("%d%d", &a, &b);
a = Find(a); b = Find(b);
pre[a] = b;
}
for(int i = 1; i <= n; i++) ve[Find(i)].push_back(val[i]);
for(int i = 1; i <= n; i++) sort(ve[i].begin(), ve[i].end());
ll dp[33]; ll ans = 0;
for(int i = 1; i <= n; i++){
if(ve[i].size() <= 1) continue;
memset(dp, 0, sizeof(dp));
for(int k = 0; k < (int)ve[i].size(); k++){
int v = ve[i][k];
for(int j = 0; j < 30; j++){
if(v & (1 << j)) {
ans += dp[j] * 1ll * v % MOD * (1 << j) % MOD; ans %= MOD;
dp[j]++; if(dp[j] >= MOD) dp[j] -= MOD;
}
}
}
}
printf("%lld\n", ans);
}
return 0;
}