【CF578F】Mirror Box(矩阵树定理)
- 一张\(n\times m\)的网格图,每个格子上可能有一个镜子
/
或\
,也可能是*
表示可任填。 - 问有多少种摆镜子的方案,满足光线从边界上的任意一条边射入都能从与之相邻的边射出,且网格中每一条边都至少被一条光线穿过。
- \(n,m\le100\),
*
的数量\(\le200\)
玄妙的性质+矩阵树定理
非常玄妙的一道题。
考虑这道题的本质其实是在网格图\((n+1)\times(m+1)\)的顶点点阵上连边。
由于镜子都是斜的,因此连边也必然是斜的,所以这是一张二分图,我们可以把它黑白染色。
然后发现第一个条件意味着要将边界上的边两两分组且同组的边是连通的,而第二个条件意味着不能存在环。
也就是说,对于黑白染色得到的两张图,我们只要分别求出它们生成树的个数然后加起来即可(显然对于一张图的生成树,另一张图中的边的连法是唯一的)。
对于已经存在的边,我们可以用并查集合并相连的点,注意若这一过程有环就直接输出\(0\)。
然后就是矩阵树定理裸题了。
代码:\(O(nm\alpha(nm)+k^3)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define K 1000
#define T (N+1)*(N+1)
#define P(x,y) ((x)*(m+1)+(y)+1)
using namespace std;
int n,m,X,tot,id[T+5];char s[N+5][N+5];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
namespace UnionFindSet//并查集
{
int f[T+5];I int fa(CI x) {return f[x]?f[x]=fa(f[x]):x;}
I void U(RI x,RI y) {(x=fa(x))^(y=fa(y))?f[x]=y:(puts("0"),exit(0),0);}//有环直接输出0
}
class Gauss
{
private:
int op,a[K+5][K+5];I bool Find(CI x)
{
RI i=x+1,j;W(i<=n&&!a[i][x]) ++i;if(i>n) return 0;
for(j=x;j^n;++j) swap(a[i][j],a[x][j]);return 1;
}
public:
int n;I void A(CI x,CI y) {++a[x][x],++a[y][y],--a[x][y],--a[y][x];}//把一条边转化到矩阵上
I int Det()//行列式求值
{
RI i,j,k,t;for(i=1;i^n;++i) for(j=1;j^n;++j) a[i][j]=(a[i][j]%X+X)%X;
for(i=1;i^n;++i) if(!a[i][i]&&(op^=1,!Find(i))) break;else for(j=i+1;j^n;++j)
for(t=X-1LL*a[j][i]*QP(a[i][i],X-2)%X,k=i;k^n;++k) a[j][k]=(1LL*a[i][k]*t+a[j][k])%X;
for(t=op?X-1:1,i=1;i^n;++i) t=1LL*t*a[i][i]%X;return t;
}
}G1,G2;
int main()
{
using namespace UnionFindSet;RI i,j;scanf("%d%d%d",&n,&m,&X);
for(i=1;i<=n;++i) for(scanf("%s",s[i]+1),j=1;j<=m;++j)
s[i][j]=='/'&&(U(P(i,j-1),P(i-1,j)),0),s[i][j]=='\\'&&(U(P(i-1,j-1),P(i,j)),0);//对于初始边并查集合并
for(i=0;i<=n;++i) for(j=0;j<=m;++j) (i^j)&1&&//第一张图重标号
(!id[fa(P(i,j))]&&(id[fa(P(i,j))]=++G1.n),id[P(i,j)]=id[fa(P(i,j))]);
for(i=0;i<=n;++i) for(j=0;j<=m;++j) (i^j)&1^1&&//第二张图重标号
(!id[fa(P(i,j))]&&(id[fa(P(i,j))]=++G2.n),id[P(i,j)]=id[fa(P(i,j))]);
for(i=1;i<=n;++i) for(j=1;j<=m;++j) if(s[i][j]=='*') (i^j)&1//对于没确定的方格连两条边
?(G2.A(id[P(i,j-1)],id[P(i-1,j)]),G1.A(id[P(i-1,j-1)],id[P(i,j)]))
:(G1.A(id[P(i,j-1)],id[P(i-1,j)]),G2.A(id[P(i-1,j-1)],id[P(i,j)]));
return printf("%d\n",(G1.Det()+G2.Det())%X),0;//两张图生成树个数之和
}
待到再迷茫时回头望,所有脚印会发出光芒