题目
题目描述
给出一个仙人掌图,求其邻接矩阵的行列式。对于一些术语的说明如下:
- (无向图上的)简单环:一个点与边的交错序列 ⟨ x 1 , e 1 , x 2 , e 2 , … , x k , e k , x k + 1 ⟩ \langle x_1,e_1,x_2,e_2,\dots,x_{k},e_k,x_{k+1}\rangle ⟨x1,e1,x2,e2,…,xk,ek,xk+1⟩,满足边 e i ( 1 ⩽ i ⩽ k ) e_i\;(1\leqslant i\leqslant k) ei(1⩽i⩽k) 连接点 x i x_i xi 和 x i + 1 x_{i+1} xi+1,且 x 1 = x k + 1 x_1=x_{k+1} x1=xk+1,且任意 1 ⩽ i < j ⩽ k 1\leqslant i<j\leqslant k 1⩽i<j⩽k 满足 x i ≠ x j x_i\ne x_j xi=xj 。
- 仙人掌图:一个无向简单连通图,满足每条边至多属于一个 点数超过三 的简单环。即对于任意边 e e e,至多存在一个简单环(一个交错序列),其中包含至少 3 3 3 条边,且 e e e 属于该交错序列。
- 邻接矩阵:对于 n n n 个点的简单无向图,其邻接矩阵 A A A 大小为 n × n n\times n n×n,满足 A i , j = 1 ( i ≠ j ) A_{i,j}=1\;(i\ne j) Ai,j=1(i=j) 当且仅当图中 i , j i,j i,j 之间有边。
数据范围与提示
点数
n
⩽
1
0
5
n\leqslant 10^5
n⩽105 而边数
m
⩽
2
×
1
0
5
m\leqslant 2\times 10^5
m⩽2×105 。
思路
根据行列式的定义,相当于给每个点选择一个出度,使得每个点恰好有一个入度。换句话说,找出原图的环剖分。其中两个点可以构成二元环(如果二者之间有边)。
那么,由于一条边只会属于一个简单环(为了方便,不认为二元环是简单环),如果一条边不是双向的,那么只有一种方式将其 “圆回来”。所以说,要么是选择一个大环,要么是一条边的两个端点互相连接。
然而逆序对怎么算?完全不会啊。以前学过的东西都忘光了啊……
逆序对的奇偶性有一个简单的判断方法:任意交换元素,想要使得序列有序,交换次数的奇偶性就是逆序对的奇偶性。那么,对于一个环, { i } = { f ( i ) } \{i\}=\{f(i)\} {i}={f(i)} 即自变量和因变量是双射,可以考虑直接使得 a i = i a_i=i ai=i 的交换次数,这样就 避免了环与外部的逆序对数量计算!这一点很重要。
考虑将 a i a_i ai 和 a a i a_{a_i} aai 交换,则有 a i ′ = a a i a'_i=a_{a_i} ai′=aai 而 a a i ′ = a i a'_{a_i}=a_i aai′=ai,显然 a i a_i ai 位置上的数字就正确了,而剩下的数字仍然构成环双射。不断进行,你就会发现:对于大小为 k k k 的环,进行 ( k − 1 ) (k-1) (k−1) 次交换可以使得所有数字位置正确。
于是系数就求出来了!选择环的系数是 2 × ( − 1 ) k 2\times(-1)^k 2×(−1)k,其中 2 2 2 是顺时针与逆时针的区别;选择二元环(一条边)的系数是 ( − 1 ) (-1) (−1),因为此时不分顺时针与逆时针了。
然后考虑我们 最喜欢的 仙人掌
d
p
\tt dp
dp 。这东西其实很简单:可以想象成你建出了圆方树。也就是说,对于任意一个环,直接将其视为基环树,做基环树的环上
d
p
\tt dp
dp 即可。而对于一个点,与它相邻的所有环(包括二元环;本质上是所有方点)的地位都是相等的,都视作树形
d
p
\tt dp
dp 从儿子转移而来即可。
时间复杂度 O ( n ) \mathcal O(n) O(n),代码实现颇为费脑……
代码
#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MOD = 993244853;
inline int modAdd(const int &x,const int &y){
return (x+y >= MOD) ? (x+y-MOD) : (x+y);
}
inline void modAddUp(int &x,const int &y){
if((x += y) >= MOD) x -= MOD;
}
inline int qkpow(int_ b,int q){
int_ a = 1;
for(; q; q>>=1,b=b*b%MOD)
if(q&1) a = a*b%MOD;
return static_cast<int>(a);
}
const int MAXN = 100005;
struct Edge{
int to, nxt;
Edge() = default;
Edge(int _to,int _nxt)
:to(_to),nxt(_nxt){ }
};
Edge e[MAXN<<2];
int head[MAXN], cntEdge;
void addEdge(int a,int b){
e[cntEdge] = Edge(b,head[a]);
head[a] = cntEdge ++;
}
int dp[MAXN][2]; ///< 0: unoccupied
void workOnArray(int now[],int a[],int n){
int nxt[2]; // helper dp value
for(int i=1; i<=n; ++i,now[0]=nxt[0],now[1]=nxt[1]){
nxt[0] = int(int_(now[1])*dp[a[i]][0]%MOD);
nxt[1] = int((int_(now[1])*dp[a[i]][1]
+MOD-int_(now[0])*dp[a[i]][0]%MOD)%MOD);
}
}
void workOnCircle(int a[],int n){
const int &o = a[n]; // root
int all = 1; // non-occupy (choose cycle)
rep(i,1,n) all = int(int_(all)*dp[a[i]][0]%MOD);
int one[2] = {0,1}, two[2] = {0};
workOnArray(one,a,n); // if a[1] not connect with a[n]
two[1] = int(MOD-int_(dp[a[1]][0])*dp[o][0]%MOD);
if(n == 2) two[1] = 0, all = 0;
workOnArray(two,a+1,n-2); // if a[1] connect to o
dp[o][0] = one[0]; // the only case
if((all <<= 1) >= MOD) all -= MOD;
if(!(n&1) && all) all = MOD-all;
dp[o][1] = modAdd(modAdd(one[1],two[1]),all);
}
int dfn[MAXN], low[MAXN], dfsClock;
int sta[MAXN], top, now[MAXN], siz;
void dfs(int x,int pre){
dfn[x] = low[x] = ++ dfsClock; sta[++ top] = x;
dp[x][0] = 1, dp[x][1] = 0; // not matched
for(int i=head[x]; ~i; i=e[i].nxt)
if(e[i].to != pre){
if(!dfn[e[i].to]){
dfs(e[i].to,x);
low[x] = min(low[x],low[e[i].to]);
if(low[e[i].to] >= dfn[x]){
for(siz=0; now[siz]!=e[i].to; )
now[++ siz] = sta[top --];
now[++ siz] = x; // cut point itself
workOnCircle(now,siz);
}
}
else low[x] = min(low[x],dfn[e[i].to]);
}
}
int main(){
int n = readint(), m = readint();
memset(head+1,-1,n<<2);
for(int a,b; m; --m){
a = readint(), b = readint();
addEdge(a,b), addEdge(b,a);
}
dfs(1,0); printf("%d\n",dp[1][1]);
return 0;
}