构造如下一张无向图:
1.点集大小为$(n+1)(m+1)$,即所有格点
2.边集大小为$nm$,即所有镜子所连结的两个格点
对于一个确定的镜子状态,即可确定上图,那么来考虑什么样的图是合法的
结论:如果将这些点黑白染色,显然不存在连结黑色和白色点的边,之后合法当且仅当黑色点恰好构成生成树或白色点恰好构成生成树
由于两者不可能同时构成生成树(这意味着有$(n+1)(m+1)-2$条边,边数不足)
以黑色为例,对于一个未确定的镜子,也就是一条边是否存在,不难发现这就是一个生成树计数,对于强制存在的边预先缩点即可,由于最后至多新增$k$条边,即若缩点后连通块数多于$k+1$无解($k$为$*$个数)
根据矩阵树定理计算,复杂度显然是$o(k^{3})$,缩点复杂度为$o(nmlog nm)$,即可通过
下面考虑前面的结论,简单的说明一下:
为了方便,将最外面的一圈边界也看作镜子并连边,然后即构成了一张平面图
对于平面图中的封闭图形,显然光线无法穿过多个封闭图形,接下来我们证明一个封闭图形中恰好有一条光线,且覆盖了其中所有位置
证明比较简单,只需要找到一个与光线相邻且未被覆盖的位置,从该处引出一条光线就会导致矛盾
换言之,与边界线在同一个封闭图形内的另一个出口就是其结束的位置
这也就等价于边界上所有黑点(或白点)相邻两点连通,那么即构成一个仅含相邻两段边界线的封闭图形,同时如果不包含所有黑点,那么外面这一圈相邻两点的路径有交,必然构成一个不包含边界的封闭图形,无法被覆盖
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define N 105 4 vector<pair<int,int> >v; 5 int n,m,k,mod,a[N<<1][N<<1],pos[N*N],f[N*N]; 6 char s[N][N]; 7 int id(int x,int y){ 8 return x*(m+1)+y+1; 9 } 10 int find(int k){ 11 if (k==f[k])return k; 12 return f[k]=find(f[k]); 13 } 14 bool merge(int x,int y){ 15 x=find(x),y=find(y); 16 if (x==y)return 0; 17 f[x]=y; 18 return 1; 19 } 20 int pow(int n,int m){ 21 int s=n,ans=1; 22 while (m){ 23 if (m&1)ans=1LL*ans*s%mod; 24 s=1LL*s*s%mod; 25 m>>=1; 26 } 27 return ans; 28 } 29 int guess(int n){ 30 int ans=1; 31 for(int i=1;i<=n;i++){ 32 int k=-1; 33 for(int j=i;j<=n;j++) 34 if (a[j][i]){ 35 k=j; 36 break; 37 } 38 if (k<0)return 0; 39 if (k!=i){ 40 ans=mod-ans; 41 for(int j=i;j<=n;j++)swap(a[i][j],a[k][j]); 42 } 43 ans=1LL*ans*a[i][i]%mod; 44 int s=pow(a[i][i],mod-2); 45 for(int j=i;j<=n;j++)a[i][j]=1LL*a[i][j]*s%mod; 46 for(int j=i+1;j<=n;j++){ 47 int s=a[j][i]; 48 for(int k=i;k<=n;k++)a[j][k]=(a[j][k]-1LL*s*a[i][k]%mod+mod)%mod; 49 } 50 } 51 return ans; 52 } 53 int calc(int p){ 54 for(int i=0;i<=n;i++) 55 for(int j=0;j<=m;j++) 56 if ((i+j)%2==p)f[id(i,j)]=id(i,j); 57 v.clear(); 58 for(int i=0;i<n;i++) 59 for(int j=0;j<m;j++) 60 if ((i+j)%2==p){ 61 if (s[i][j]=='*')v.push_back(make_pair(id(i,j),id(i+1,j+1))); 62 if (s[i][j]=='\'){ 63 if (!merge(id(i,j),id(i+1,j+1)))return 0; 64 } 65 } 66 else{ 67 if (s[i][j]=='*')v.push_back(make_pair(id(i,j+1),id(i+1,j))); 68 if (s[i][j]=='/'){ 69 if (!merge(id(i,j+1),id(i+1,j)))return 0; 70 } 71 } 72 pos[0]=0; 73 for(int i=0;i<=n;i++) 74 for(int j=0;j<=m;j++) 75 if (((i+j)%2==p)&&(f[id(i,j)]==id(i,j)))pos[id(i,j)]=++pos[0]; 76 if (pos[0]>k+1)return 0; 77 memset(a,0,sizeof(a)); 78 for(int i=0;i<v.size();i++){ 79 int x=pos[find(v[i].first)],y=pos[find(v[i].second)]; 80 if (x<pos[0])a[x][x]=(a[x][x]+1)%mod; 81 if (y<pos[0])a[y][y]=(a[y][y]+1)%mod; 82 if ((x<pos[0])&&(y<pos[0])){ 83 a[x][y]=(a[x][y]+mod-1)%mod; 84 a[y][x]=(a[y][x]+mod-1)%mod; 85 } 86 } 87 return guess(pos[0]-1); 88 } 89 int main(){ 90 scanf("%d%d%d",&n,&m,&mod); 91 for(int i=0;i<n;i++){ 92 scanf("%s",s[i]); 93 for(int j=0;j<m;j++) 94 if (s[i][j]=='*')k++; 95 } 96 printf("%d",(calc(0)+calc(1))%mod); 97 }