「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)\) 上的若干个线性空间:
-
边空间:幂集 \(2^E\) 对应的线性空间,干脆叫 \(\mathcal E\)。
-
回路空间:图上的回路 \(C\) 是一个使得每个点在 \(C\) 中邻接边的条数均为偶数的 \(E\) 的子集。因为两个回路做对称差之后仍然是回路,所以回路也可以对应到一个线性空间 \(\mathcal C\) 且为 \(\mathcal E\) 的子空间。
-
割空间:若 \(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;
}
标签:int,subseteq,DZJ,omega,II,read,回路,mathcal,BZOJ3569 来源: https://www.cnblogs.com/crashed/p/16473418.html