[海军国际项目办公室]仙人掌
仙人掌
题目概述
题解
一看到线性代数就知道我考场上肯定做不来。
如果对于矩阵直接求出它的行列式的话即使原矩阵十分的稀疏,我们最多也只能得到
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;
}