zoukankan      html  css  js  c++  java
  • 「CF724G」Xor-matic Number of the Graph「线性基」

    题意

    求所有点对(u,v)(u)(v)所有不同的异或路径的异或值之和,对(10^9+7)取模

    题解

    求出一个dfs树,那么(u)(v)的路径一定是树上路径异或一些环。这些环只可能是返祖边构成的,我们把所有环存到线性基里。

    先把每一位拆开,答案变为:(sum_{i = 0}^{60} 2^i f(i)),其中(f(i))表示所有满足要求的路径中,第(i)位是(1)的路径个数

    考虑(f(i))怎么求。枚举(u)(v),我们假设dfs的时候预处理了一个数组(d),表示到根的异或值

    (b)表示线性基里异或得到的数,那么问题转换为(d[u] ext{ xor } d[v] ext{ xor } b)有多少种情况第(i)位为(1)

    再令(x,y)分别表示(d[u],d[v])的二进制第(i)位,令线性基中第(i)位是(1)的个数为(c_1),第(i)位是(0)的个数为(c_0)

    (c_1=0),意味着线性基对答案没有贡献,随便取,对(f(i))的贡献是(2^{c_0}[x ext{ xor } y = 1])

    (c_1>0),则线性基有贡献。(x ext{ xor } y = 1)(c_1)要取奇数个,否则取偶数个

    根据组合数学,(n)个数里面选偶数个数和选奇数个数的方案数都是(2^{n-1})

    所以这种情况对(f(i))的贡献是(2^{c_0}2^{c_1-1})

    上面的式子综合一下,(f(i) = sum_{1leq u < v leq n} ...)(不写了)

    然后我们把枚举的过程省掉。第一种情况等价于求多少对(u,v)异或起来第(i)位是(1),直接记录(cnt[i])表示(d[u]=i)(u)个数,然后答案就是(frac{cnt[i] (n - cnt[i])}{2})。第二种情况实际上是所有点对都适用,即为(frac{n(n-1)}{2})

    这题就做完了,不过要注意一下图不一定连通,每个连通块分别求,上面公式中的(n)也要改为块的大小

    #include <algorithm>
    #include <cstdio>
    using namespace std;
    
    typedef long long ll;
    
    const int N = 4e5 + 10;
    const int mo = 1e9 + 7;
    
    struct Edge {
    	int v, nxt; ll w;
    } e[N];
    int n, m, hd[N], ec;
    void clr() {
    	fill(hd + 1, hd + n + 1, -1); ec = 0;
    }
    void add(int u, int v, ll w) {
    	e[ec] = (Edge) {v, hd[u], w}; hd[u] = ec ++;
    }
    
    namespace base {
    
    ll b[61];
    int sz, cnt[61];
    
    void clr() {
    	sz = 0;
    	fill(b, b + 61, 0);
    	fill(cnt, cnt + 61, 0);
    }
    
    void ins(ll x) {
    	for(int i = 60; i >= 0; i --) {
    		if(x & (1ll << i)) {
    			if(b[i]) x ^= b[i];
    			else {
    				b[i] = x; sz ++;
    				for(int j = i - 1; j >= 0; j --) if(b[i] >> j & 1) b[i] ^= b[j];
    				for(int j = i + 1; j <= 60; j ++) if(b[j] >> i & 1) b[j] ^= b[i];
    				break ;
    			}
    		}
    	}
    }
    
    void solve() {
    	for(int i = 60; i >= 0; i --) if(b[i])
    		for(int j = i; j >= 0; j --) if(b[i] >> j & 1) cnt[j] ++;
    }
    
    }
    
    ll d[N], cnt[61], bo;
    bool vis[N];
    void dfs(int u, int pre = -404) {
    	vis[u] = 1; bo ++;
    	for(int i = 60; i >= 0; i --)
    		if(d[u] & (1ll << i)) cnt[i] ++;
    	for(int i = hd[u]; ~ i; i = e[i].nxt) if(i != pre) {
    		int v = e[i].v; ll w = e[i].w;
    		if(!vis[v]) {
    			d[v] = d[u] ^ w;
    			dfs(v, i ^ 1);
    		} else {
    			base :: ins(w ^ d[u] ^ d[v]);
    		}
    	}
    }
    int main() {
    	scanf("%d%d", &n, &m); clr();
    	int u, v; ll w;
    	for(int i = 1; i <= m; i ++) {
    		scanf("%d%d%I64d", &u, &v, &w);
    		add(u, v, w); add(v, u, w);
    	}
    	ll ans = 0;
    	for(int u = 1; u <= n; u ++) if(!vis[u]) {
    		base :: clr(); bo = 0; fill(cnt, cnt + 61, 0);
    		dfs(u); base :: solve();
    		for(int i = 0; i <= 60; i ++) {
    			ll res = 0;
    			if(!base :: cnt[i])	{
    				int c0 = base :: sz - base :: cnt[i];
    				res = (1ll << c0) % mo * (cnt[i] * (bo - cnt[i]) % mo) % mo;
    			} else {
    				res = (1ll << (base :: sz - 1)) % mo * (bo * (bo - 1) / 2 % mo) % mo;
    			}
    			if(res) (ans += (1ll << i) % mo * res % mo) %= mo;
    		}
    	}
    	printf("%d
    ", (int) ans);
    	return 0;
    }
    
    
  • 相关阅读:
    ASP.NET中使用javascript
    遍历DataList控件
    史上最强劲之android模拟器命令详解
    Android开发环境配置简介
    Android模拟器adb命令介绍
    听一名普通android应用开发人员谈:怎样成为一名Android开发者
    android模拟器安装及优化(集锦)
    Ubuntu 快捷键集锦
    smplayer 中文字幕乱码,进度条及拖放MKV
    如何在Windows下搭建Android开发环境
  • 原文地址:https://www.cnblogs.com/hongzy/p/11258053.html
Copyright © 2011-2022 走看看