• 「BZOJ3569」DZJ Loves Chinese II


    题目

    点这里看题目。

    分析

    神奇的题目啊!

    以下设被删除的边集为 \(Q\)

    思路一

    正常人的思路。

    随便拉一棵生成树 \(T\),并定一个根。假如我们只删除了一条树边 \(e\),设 \(S(e)\) 为覆盖 \(e\) 的非树边的集合,则图不连通当且仅当 \(Q\supseteq S(e)\)

    那么删除了多条树边呢?假如我们钦定了一个树边集合 \(Q_T\subseteq Q\)\(Q_T\) 中的边对应的子树两两不相交,则我们将上一步的方法拓展,我们只需要知道是否有 \(Q\supseteq(\Delta_{e\in Q_T}S(e))\),其中 \(\Delta\) 表示集合的对称差。由于子树两两不交,因此跨子树的边会被差掉。

    合理地猜测一下,我们可以猜想在 \(Q_T\) 中边的子树存在包含关系时,类似的想法仍然成立——假如令非树边的 \(S(e)=\{e\}\),则我们只需要检查是否存在 \(Q'\subseteq Q,Q'\neq \varnothing\), 使得 \(\Delta_{e\in Q'}S(e)=\varnothing\)。(旧的想法需要限制 \(Q'\) 中树边的子树互不相交)

    至少我并没能很快地建立一个证明(似乎只需要证明,不符合第二步条件的 \(Q'\) 不会影响结果即可)。不过我们验证的方法很多,比如疯狂对拍。没有试一下这个推广过后的想法对不对实在是可惜。


    具体实现起来,可以做一个简单的 hash,用随机权值的异或替代集合的异或。给一个非树边的权值 \(w:E\subseteq T\rightarrow \{0,1\}^\omega\),则我们可以定义 \(f(e)=\bigoplus_{x\in S(e)}w(x)\)。询问只需要检查 \(Q\) 中的是否有异或为 \(0\) 的权值子集即可。

    复杂度可以做到 \(O(n+\omega qk)\)

    Remark.

    随机赋权这个思路......其实挺常见的?

    hash 检查的目的就是要简化运算,代价是降低了正确概率。“赋权”是一种 hash,而“随机”就是保证正确率。此外,在赋权之后还要建立运算的映射关系。

    做一些判断的时候,不要忘记了这种方法吧。

    思路二

    学过线性代数的人的思路。我没学过,抄的

    给出一个简单无向连通图 \(G=(V,E)\),定义 \(\mathbb{GF}(2)\) 上的若干个线性空间:

    1. 边空间:幂集 \(2^E\) 对应的线性空间,干脆叫 \(\mathcal E\)

    2. 回路空间:图上的回路 \(C\) 是一个使得每个点在 \(C\) 中邻接边的条数均为偶数的 \(E\) 的子集。因为两个回路做对称差之后仍然是回路,所以回路也可以对应到一个线性空间 \(\mathcal C\) 且为 \(\mathcal E\) 的子空间。

    3. 割空间:若 \(D\subseteq E\) 是一个割,当且仅当存在 \(S\subseteq V\),使得 \(D=\{(u,v)|u\in S,v\in V\setminus S\}\)。由于 \(D\) 实质上就是 \(S\) 中点的邻接边的对称差,因此两个割对称差之后还是割,所以割也可以对应到线性空间 \(\mathcal D\) 且为 \(\mathcal E\) 的子空间。

    随便从 \(G\) 上面拉一棵生成树 \(T\) 出来,并定一个根。在这个基础上,有:

    • \(\mathcal C\) 的一组基由 \(T\) 上的所有“简单环”构成。

      简单环就是指一条非树边 \(e=(u,v)\)\(T\)\(u\)\(v\) 的路径上的边共同构成的一个回路。

      这个东西比较浅显易懂。经典的 「WC2011」最大 XOR 和路径 可以算是用到了这个东西。

    • \(\mathcal D\) 的一组基由 \(T\) 上所有子树(不包括 \(T\) 本身)对应的点集对应的割构成。

      Explanation.

      首先,容易发现两个割不同等价于生成它们的点集不同(虽然有两种点集,不过大概是这个意思)。这样就容易发现由子树生成的割彼此“线性无关”。

      另一方面,我们不难构造一种方案,来用 \(T\) 上所有子树构造处某一个割的一部点集。(从最浅的元素开始构造即可)

    • 对于 \(E_1,E_2\subseteq E\),可以发现对应的向量的内积为 \(|E_1\cap E_2|\mod 2\)

      那么,可以证明,对于任意一个回路 \(C\) 和割 \(D\),二者对应的向量必然正交

      Explanation.

      对于任何一个 \(C\),考虑其中的所有环 \(\mathring C\)(环既是回路,也是路径)。一个环 \(\mathring C\) 必然和 \(D\) 交出偶数条边。环在回路中彼此不交,因而回路也会和 \(D\) 交出偶数条边。

    • 在线性空间 \(\mathcal E\) 中,\(\mathcal C\)\(\mathcal D\) 互为正交补。

      正交补就是“与某个子空间的向量均正交的空间内向量构成的空间“。

      Explanation.

      根据对基的考察,我们有 \(\dim \mathcal C=|E|-|V|+1,\dim \mathcal D=|V|-1\)


    这道题就是给出了 \(Q\),询问 \(Q\) 对应的向量是否 \(\in \mathcal D\)

    事实上根据对于 \(\mathcal D\) 的基的讨论就已经足够解决这个问题了。拉一棵生成树 \(T\) 并定根,我们对于每条树边 \(e\),算出覆盖它的边的集合 \(S'(e)\)(包括它自己)。则我们只需要检查是否存在一个 \(P\subseteq T,Q'\subseteq Q\),使得 \(\Delta_{e\in P}S'(e)=Q'\) 即可。

    事实上,我们已经可以从这里生成“思路一”的解法了。稍加修改即可。

    顺便补充一下正确概率:

    Note.

    我们相当于要算 \(S\neq \varnothing\)\(f(S)=0\) 的概率。

    呃......似乎很容易。从 \(S\) 中去掉一个元素 \(x\),则这就是要求 \(w(x)=w(S\setminus\{x\})\)。这样可以粗略估得概率为 \(2^{-\omega}\)

    所以,如果要求出错概率为 \(\varepsilon_p\),则 \(\omega=\log_2\varepsilon_p^{-1}\)。若计算机位宽为 \(\omega_0\),则复杂度大概就是 \(O(\frac{\omega}{\omega_0}(n+qk))\)

    代码

    #include <cmath>
    #include <cstdio>
    #include <random>
    
    #define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
    #define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
    
    typedef unsigned long long ull;
    
    const int MAXN = 1e5 + 5, MAXM = 5e5 + 5, MAXLOG = 18;
    
    template<typename _T>
    void read( _T &x ) {
    	x = 0; char s = getchar(); int f = 1;
    	while( ! ( '0' <= s && s <= '9' ) ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
    	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
    	x *= f;
    }
    
    template<typename _T>
    void write( _T x ) {
    	if( x < 0 ) putchar( '-' ), x = -x;
    	if( 9 < x ) write( x / 10 );
    	putchar( x % 10 + '0' );
    }
    
    struct Edge {
    	int to, nxt;
    } Graph[MAXM << 1];
    
    ull bas[64];
    int seq[20], tot;
    
    int head[MAXN], cnt = 1;
    ull tag[MAXN];
    
    int fr[MAXM], to[MAXM];
    bool onTre[MAXM];
    ull wei[MAXM];
    
    bool vis[MAXN];
    
    int N, M, Q, lg2;
    
    inline void AddEdge( const int &from, const int &to ) {
    	Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
    	head[from] = cnt;
    }
    
    void DFS( const int &u ) {
    	vis[u] = true;
    	for( int i = head[u], v ; i ; i = Graph[i].nxt )
    		if( ! vis[v = Graph[i].to] ) 
    			onTre[i >> 1] = true, DFS( v ); 
    }
    
    void Recover( const int &u, const int &fa ) {
    	for( int i = head[u], v ; i ; i = Graph[i].nxt )
    		if( onTre[i >> 1] && ( v = Graph[i].to ) ^ fa ) {
    			Recover( v, u );
    			tag[u] ^= tag[v];
    			wei[i >> 1] = tag[v];
    		}
    }
    
    inline bool Insert( ull x ) {
    	per( i, 63, 0 ) {
    		if( ! ( x >> i & 1 ) ) continue;
    		if( ! bas[i] ) { bas[i] = x; return true; }
    		x ^= bas[i];
    	}
    	return false;
    }
    
    int main() {
    	static std :: mt19937_64 rng( 998244853 );
    	static std :: uniform_int_distribution<ull> genHash( 0, ( ull ) -1 );
    
    	read( N ), read( M );
    	rep( i, 1, M ) {
    		read( fr[i] ), read( to[i] );
    		AddEdge( fr[i], to[i] );
    		AddEdge( to[i], fr[i] );
    	}
    	DFS( 1 );
    	rep( i, 1, M ) if( ! onTre[i] ) {
    		wei[i] = genHash( rng );
    		tag[fr[i]] ^= wei[i];
    		tag[to[i]] ^= wei[i];
    	}
    	Recover( 1, 0 );
    	int ansCount = 0;
    	for( read( Q ) ; Q -- ; ) {
    		read( tot );
    		rep( i, 1, tot ) read( seq[i] ), seq[i] ^= ansCount;
    		bool flg = true;
    		rep( i, 0, 63 ) bas[i] = 0;
    		rep( i, 1, tot ) flg &= Insert( wei[seq[i]] );
    		if( flg ) puts( "Connected" ), ansCount ++;
    		else puts( "Disconnected" );
    	}
    	return 0;
    }
    
  • 相关阅读:
    windows下查看端口占用情况及关闭相应的进程
    python学习中的一些“坑”
    python 中一些关键字的区别
    linux下配置Tomcat开机启动
    windows 下的python 安装pycrypto
    'redis-server' 不是内部或外部命令,也不是可运行的程序或批处理文件
    怎么学习代码
    crx文件不能安装,提示无效的安装包
    回调函数
    koa-router的作用
  • 原文地址:https://www.cnblogs.com/crashed/p/16473418.html
Copyright © 2020-2023  润新知