6702. 【2020.06.07省选模拟】仙人掌

题目

给出一个仙人掌,问邻接矩阵的行列式……
n ≤ 1 e 5 n\leq 1e5 n1e5


思考历程


不存在的
直接高斯消元拿30分跑路。


正解

先分析一波行列式的性质。
行列式的式子长这样: ∑ { p i } ( − 1 ) r e v ( p ) ∏ i A i , p i \sum_{\{p_i\}}(-1)^{rev(p)}\prod_{i}A_{i,p_i} {pi}(1)rev(p)iAi,pi
重要性质:对于一个排列 { p i } \{p_i\} {pi} i i i p i p_i pi连边, ( − 1 ) 偶 环 个 数 = ( − 1 ) r e v ( p ) (-1)^{偶环个数}=(-1)^{rev(p)} (1)=(1)rev(p)

证明:
众所周知,矩阵交换两行(或两列)之后,行列式取反。
假如将两个点的编号交换,那么相当于交换了两行和两列,行列式不变。
于是形成了若干个优美的环,每个的环的形式为 1 → 2 , 2 → 3 , . . . , L → 1 1\to 2,2\to 3,...,L\to 1 12,23,...,L1
只有最后一个有 ( − 1 ) L − 1 (-1)^{L-1} (1)L1的贡献,于是只有偶环有贡献。
偶环个数就是 − 1 -1 1的指数。

将式子放到邻接矩阵中。
如果有贡献,那么 A i , p i A_{i,p_i} Ai,pi不为 0 0 0,即 ( i , p i ) ∈ E (i,p_i)\in E (i,pi)E
转化一下模型,可以变成这样:在图中选出若干个点不相交的环和若干个一条边连接着的点对,奇环贡献为 2 2 2,偶环贡献为 − 2 -2 2,点对的贡献为 − 1 -1 1。求所有方案的贡献和。
建出圆方树之后随便DP。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100010
#define M 200010
#define ll long long
#define mo 998244353
int n,m;
struct EDGE{
	int to;
	EDGE *las;
};
struct Graph{
	EDGE *last[N*2];
	void link(int u,int v){
		EDGE *nw=new EDGE;
		*nw={v,last[u]};
		last[u]=nw;
	}
} G,F;
int dfn[N],low[N],nowdfn;
int st[N],tp;
int num;
void tarjan(int x,int fa){
	low[x]=dfn[x]=++nowdfn;
	st[++tp]=x;
	for (EDGE *ei=G.last[x];ei;ei=ei->las){
		if (ei->to==fa)
			continue;
		if (dfn[ei->to])
			low[x]=min(low[x],dfn[ei->to]);
		else{
			tarjan(ei->to,x);
			low[x]=min(low[x],low[ei->to]);
			if (low[ei->to]==dfn[x]){
				++num;
				for (;st[tp]!=ei->to;--tp)
					F.link(num,st[tp]);
				F.link(num,ei->to);
				--tp;
				F.link(x,num);
			}
			else if (low[ei->to]==dfn[ei->to]){
				F.link(x,ei->to);
				--tp;
			}
		}
	}
}
int q[N];
ll f[N*2][2],h[N*2][2];
//f[x][0/1] : circle:whether x is chose  square:whether fa is chose 
void dfs(int x,int fa){
	if (x<=n){
		f[x][0]=1,f[x][1]=0;
		for (EDGE *ei=F.last[x];ei;ei=ei->las){
			dfs(ei->to,x);
			int y=ei->to;
			if (y<=n){
				f[x][1]=((-f[x][0]*f[y][0]+f[x][1]*f[y][1])%mo+mo)%mo;
				f[x][0]=f[x][0]*f[y][1]%mo;
			}
			else{
				f[x][1]=(f[x][0]*f[y][1]+f[x][1]*f[y][0])%mo;
				f[x][0]=f[x][0]*f[y][0]%mo;
			}
		}
	}
	else{
		int k=0;
		for (EDGE *ei=F.last[x];ei;ei=ei->las)
			dfs(ei->to,x);
		for (EDGE *ei=F.last[x];ei;ei=ei->las)
			q[++k]=ei->to;
		ll g=(k+1&1?2:mo-2);
		for (int i=1;i<=k;++i)
			g=g*f[q[i]][0]%mo;
		f[x][1]+=g;
		h[1][0]=f[q[1]][0],h[1][1]=f[q[1]][1];
		for (int i=2;i<=k;++i){
			int y=q[i];
			h[i][0]=h[i-1][1]*f[y][0]%mo;
			h[i][1]=((-h[i-1][0]*f[y][0]+h[i-1][1]*f[y][1])%mo+mo)%mo;
		}
		f[x][0]+=h[k][1];
		f[x][1]+=-h[k][0]+mo;
		h[1][0]=0,h[1][1]=-f[q[1]][0]+mo;
		for (int i=2;i<=k;++i){
			int y=q[i];
			h[i][0]=h[i-1][1]*f[y][0]%mo;
			h[i][1]=((-h[i-1][0]*f[y][0]+h[i-1][1]*f[y][1])%mo+mo)%mo;
		}
		(f[x][1]+=h[k][1])%=mo;
	}
}
int main(){
	freopen("cactus.in","r",stdin);
	freopen("cactus.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		G.link(u,v),G.link(v,u);
	}
	num=n;
	tarjan(1,0);
	dfs(1,0);
	printf("%lld\n",f[1][1]);
	return 0;
}

总结

之前在想行列式相关的时候,基本上没有从定义式上去思考……
学到了……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值