[ACNOI2021]仙人掌

220 篇文章 2 订阅
122 篇文章 0 订阅

题目

题目描述
给出一个仙人掌图,求其邻接矩阵的行列式。对于一些术语的说明如下:

  • (无向图上的)简单环:一个点与边的交错序列 ⟨ 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(1ik) 连接点 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 1i<jk 满足 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 n105 而边数 m ⩽ 2 × 1 0 5 m\leqslant 2\times 10^5 m2×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) (k1) 次交换可以使得所有数字位置正确。

于是系数就求出来了!选择环的系数是 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值