[海军国际项目办公室]仙人掌

仙人掌

题目概述

在这里插入图片描述
在这里插入图片描述

题解

一看到线性代数就知道我考场上肯定做不来。

如果对于矩阵直接求出它的行列式的话即使原矩阵十分的稀疏,我们最多也只能得到 50 p t s 50pts 50pts
所以我们的正解肯定不是在矩阵上直接求解,我们可以考虑通过行列式的定义求解答案。
我们考虑枚举我们可以选择的排列,我们考虑对于这个排列,它有什么的性质。

我们先不考虑原图有环的情况,就把它当成一棵树来看待。
那么如果对于点 i i i,在它这一行它选择它的父亲,那么它的父亲不可能继续向上选择,否则它就不可能被覆盖到了,因为下面的点不可能来覆盖它,否则下面的点就覆盖不到了。
所以我们只能让它的父亲将它覆盖到。
我们的这个操作可以被转化成选择一条边,使得它的 ( u , v ) (u,v) (u,v),在排列中, p u = v , p v = u p_u=v,p_v=u pu=v,pv=u
这种这样的两列对我们行列式的系数的贡献时 − 1 -1 1,应为这样会产生一个偶置换。

而当我们存在环的时候,我们会多出一种每个点都沿着环上的边的选法。
这种选法产生的置换的大小是环的大小,所以当环的大小是奇数时它的贡献为正,偶数是为负。
而由于我们可以沿着环的两个方向选,所以还有个 2 2 2的系数。
它产生的总贡献是 2 × ( − 1 ) s i z + 1 2\times (-1)^{siz+1} 2×(1)siz+1

我们现在就要通过这两种选法来找出整个图的选法。
这是显然可以进行 d p dp dp的,我们可以将它转化成一棵圆方树。
对于圆点,我们记录 d p i , 0 / 1 dp_{i,0/1} dpi,0/1表示点 i i i是否有与它子树内的点结成匹配。
而对于方点,我们记录 d p i , 0 / 1 dp_{i,0/1} dpi,0/1表示方点的环是否覆盖到了它上面的圆点。
转移方法显然,
对于圆点,当我们将 v v v合并到 u u u时,
d p u , 0 = d p u , 0 d p v , 0 , d p u , 1 = d p u , 0 d p v , 1 + d p u , 1 d p v , 0 dp_{u,0}=dp_{u,0}dp_{v,0},dp_{u,1}=dp_{u,0}dp_{v,1}+dp_{u,1}dp_{v,0} dpu,0=dpu,0dpv,0,dpu,1=dpu,0dpv,1+dpu,1dpv,0
而对于方点,我们要将方点的所有儿子都拿下来,这也就是一条链,我们在链上进行我们的 d p dp dp
状态中需要记录我们是否覆盖了我们方点父亲的圆点,然后就可以求出我们的 d p dp dp了。
注意,最后需将整个环的选择方案加入到我们的 d p u , 1 dp_{u,1} dpu,1中。
我们的 d p r t , 1 dp_{rt,1} dprt,1就是答案。

我们建圆方树与我们的 d p dp dp都是 O ( n ) O\left(n\right) O(n)的。
时间复杂度 O ( n ) O\left(n\right) O(n)

源码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 200005
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL;       
const int INF=0x3f3f3f3f;       
const int mo=993244853;
const int inv2=499122177;
const double jzm=0.997;
const int zero=10000;
const int orG=3,invG=332748118;
const double Pi=acos(-1.0);
const double eps=1e-5;
typedef pair<LL,int> pii;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
int gcd(int a,int b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int n,m,low[MAXN],dfn[MAXN],idx,head[MAXN],sta[MAXN],stak;
int dp[MAXN][2],g[MAXN][2],ct[MAXN],tot,cnt;
struct edge{int to,nxt;}e[MAXN<<1];
void addEdge(int u,int v){e[++tot]=(edge){v,head[u]};head[u]=tot;}
vector<int>G[MAXN],cir;
void tarjan(int u,int fa){
	//printf("tarjan %d from %d\n",u,fa);
	dfn[u]=low[u]=++idx;sta[++stak]=u;int siz=G[u].size();
	for(int i=0;i<siz;i++){
		int v=G[u][i];if(v==fa)continue;
		if(dfn[v]){low[u]=min(low[u],dfn[v]);continue;}
		tarjan(v,u);low[u]=min(low[u],low[v]);
		if(low[v]>=dfn[u]){
			addEdge(u,++cnt);ct[cnt]=1;
			do{addEdge(cnt,sta[stak]);ct[cnt]++;}while(sta[stak--]^v);
		}
	}
} 
void sakura(int u,int fa){
	for(int i=head[u];i;i=e[i].nxt)
		if(e[i].to^fa)sakura(e[i].to,u);
	if(u<=n){
		dp[u][0]=1;dp[u][1]=0;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to,tmp0,tmp1;tmp0=1ll*dp[u][0]*dp[v][0]%mo;
			tmp1=add(1ll*dp[u][1]*dp[v][0]%mo,1ll*dp[u][0]*dp[v][1]%mo,mo);
			dp[u][0]=tmp0,dp[u][1]=tmp1;
		}
	}
	else{
		cir.clear();int summ=1;
		for(int i=head[u];i;i=e[i].nxt){int v=e[i].to;cir.pb(v);summ=1ll*summ*dp[v][0]%mo;}
		g[0][0]=g[0][1]=1;g[1][0]=dp[cir[0]][1];g[1][1]=mo-dp[cir[0]][0];
		for(int i=2;i<ct[u];i++)
			for(int j=0;j<2;j++){
				int v=cir[i-1],ls=cir[i-2];g[i][j]=1ll*g[i-1][j]*dp[v][1]%mo;
				if(!j||i>2)Add(g[i][j],mo-1ll*dp[v][0]*dp[ls][0]%mo*g[i-2][j]%mo,mo);
			}
		dp[u][0]=g[ct[u]-1][0];dp[u][1]=g[ct[u]-1][1];
		if(ct[u]>2){
			Add(dp[u][1],mo-1ll*g[ct[u]-2][0]*dp[cir[ct[u]-2]][0]%mo,mo);
			Add(dp[u][1],1ll*((ct[u]&1)?2:mo-2)*summ%mo,mo);
		}
	}
}
signed main(){
	freopen("cactus.in","r",stdin);
	freopen("cactus.out","w",stdout);
	read(n);read(m);cnt=n;
	for(int i=1;i<=m;i++){
		int u,v;read(u);read(v);
		G[u].pb(v);G[v].pb(u);
	}
	tarjan(1,0);sakura(1,0);
	printf("%d\n",dp[1][1]);
	return 0;
}

谢谢!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值