【HDU 6411】带劲的and和 【并查集 + 二进制拆位】

版权声明:将来的你一定会感谢现在努力的你!!!! https://blog.csdn.net/qq_37383726/article/details/81905870

度度熊专门研究过“动态传递闭包问题”,他有一万种让大家爆蛋的方法;但此刻,他只想出一道简简单单的题——至繁,归于至简
度度熊有一张n个点m条边的无向图,第i个点的点权为 v i
如果图上存在一条路径使得点i可以走到点j,则称i,j是带劲的,记f(i,j)=1;否则f(i,j)=0。显然有f(i,j)=f(j,i)。
度度熊想知道求出:
i = 1 n 1 j = i + 1 n f ( i , j ) × max ( v i , v j ) × ( v i & v j )
其中&是C++中的and位运算符,如1&3=1, 2&3=2。
请将答案对 10 9 + 7 取模后输出。
第一行一个数,表示数据组数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
分析:这个很奇怪的公式,一看就知道要对其化简,不然太难解了,首先第一个 f ( i , j ) 这个好解决,我们可以并查集维护在同一个联通快内的所有点,再看第二个 m a x ( v i , v j ) ,这个的话,我们可以将每个联通快内的所有点排个序,贡献的传递关系就很明确了,最难的是第三个公式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;
}

猜你喜欢

转载自blog.csdn.net/qq_37383726/article/details/81905870