Codeforces 578F

很诡异的结论题,没有搞懂。
结论大概是对格子的顶点黑白染色后,一个镜面放置方案合法当且仅当黑顶点或白顶点形成一棵生成树(显然不会同时成立)。不会证明233。
那么可以分开黑顶点和白顶点计数。计数的时候 n ⋅ m n\cdot m nm可能很大,不过我们可以把已有的边缩起来,再用Matrix Tree定理做生成树计数,时间复杂度 O ( c 3 ) \mathcal O(c^3) O(c3) c c c ∗ * 的个数。

#include <bits/stdc++.h>
 
using namespace std;
 
typedef long long ll;
 
int MOD;
 
ll pow_mod(ll x,int k) {
  ll ans=1;
  while (k) {
  	if (k&1) ans=ans*x%MOD;
  	x=x*x%MOD;
  	k>>=1;
  }
  return ans;
}
 
int a[405][405];
 
int gause(int n) {
  int ans=1;
  for(int i=2;i<=n;i++) {
  	if (!a[i][i]) {
  		for(int j=i+1;j<=n;j++)
  		  if (a[j][i]) {
  		  	  for(int k=i;k<=n;k++) swap(a[i][k],a[j][k]);
  		  	  ans=MOD-ans;
  		  	  break;
			}
	  }
	if (!a[i][i]) return 0;
	ll inv=pow_mod(a[i][i],MOD-2);
	for(int j=i+1;j<=n;j++)
	  for(int k=n;k>=i;k--) a[j][k]=(a[j][k]-(ll)a[i][k]*inv%MOD*a[j][i]%MOD+MOD)%MOD;
  }
  for(int i=2;i<=n;i++) ans=(ll)ans*a[i][i]%MOD;
  return ans;
}
 
namespace SETS {
 
int fa[11005];
 
void init(int n) {
  for(int i=1;i<=n;i++) fa[i]=i;
}
 
int find_father(int x) {
  return (fa[x]==x)?x:fa[x]=find_father(fa[x]);
}
 
bool check(int x,int y) {
  x=find_father(x);y=find_father(y);
  return x!=y;
}
 
void merge(int x,int y) {
  x=find_father(x);y=find_father(y);
  if (x==y) return;
  fa[x]=y;
}
 
}
 
int n,m,cur[11005];
char str[105][105];
 
inline int id(int x,int y) {
  return (x-1)*(m+1)+y;
}
 
int main() {
  scanf("%d%d%d",&n,&m,&MOD);
  for(int i=1;i<=n;i++) scanf("%s",str[i]+1);
  SETS::init((n+1)*(m+1));
  int ans=0;
  bool ok=1;
  for(int i=1;i<=n;i++)
    for(int j=1;j<=m+1;j++) 
	  if ((i+j)&1) {
    	if (j>1&&str[i][j-1]=='/') {
    		if (!SETS::check(id(i,j),id(i+1,j-1))) ok=0;
    		SETS::merge(id(i,j),id(i+1,j-1));
		} 
		if (j<=m&&str[i][j]=='\\') {
			if (!SETS::check(id(i,j),id(i+1,j+1))) ok=0;
			SETS::merge(id(i,j),id(i+1,j+1));
		} 
	  }
  if (ok) {
  	memset(a,0,sizeof(a));
  	int sz=0;
  	for(int i=1;i<=n+1;i++)
  	  for(int j=1;j<=m+1;j++)
  	    if (((i+j)&1)&&SETS::find_father(id(i,j))==id(i,j)) cur[id(i,j)]=++sz;
  	if (sz<=401) {
    for(int i=1;i<=n;i++)
      for(int j=1;j<=m+1;j++) 
	    if ((i+j)&1) {
    	  if (j>1&&str[i][j-1]=='*'&&SETS::check(id(i,j),id(i+1,j-1))) {
    	  	  int u=cur[SETS::find_father(id(i,j))],v=cur[SETS::find_father(id(i+1,j-1))];
    	  	  a[u][u]=(a[u][u]+1)%MOD;
    	  	  a[v][v]=(a[v][v]+1)%MOD;
    	  	  a[u][v]=(a[u][v]-1+MOD)%MOD;
    	  	  a[v][u]=(a[v][u]-1+MOD)%MOD;
		  } 
		  if (j<=m&&str[i][j]=='*'&&SETS::check(id(i,j),id(i+1,j+1))) {
    	  	  int u=cur[SETS::find_father(id(i,j))],v=cur[SETS::find_father(id(i+1,j+1))];
    	  	  a[u][u]=(a[u][u]+1)%MOD;
    	  	  a[v][v]=(a[v][v]+1)%MOD;
    	  	  a[u][v]=(a[u][v]-1+MOD)%MOD;
    	  	  a[v][u]=(a[v][u]-1+MOD)%MOD;
		  } 
	    }
	ans=(ans+gause(sz))%MOD;
    }
  }
  ok=1;
  for(int i=1;i<=n;i++)
    for(int j=1;j<=m+1;j++) 
	  if (!((i+j)&1)) {
    	if (j>1&&str[i][j-1]=='/') {
    		if (!SETS::check(id(i,j),id(i+1,j-1))) ok=0;
    		SETS::merge(id(i,j),id(i+1,j-1));
		} 
		if (j<=m&&str[i][j]=='\\') {
			if (!SETS::check(id(i,j),id(i+1,j+1))) ok=0;
			SETS::merge(id(i,j),id(i+1,j+1));
		} 
	  }
  if (ok) {
  	memset(a,0,sizeof(a));
  	int sz=0;
  	for(int i=1;i<=n+1;i++)
  	  for(int j=1;j<=m+1;j++)
  	    if (!((i+j)&1)&&SETS::find_father(id(i,j))==id(i,j)) cur[id(i,j)]=++sz;
  	if (sz<=401) {
    for(int i=1;i<=n;i++)
      for(int j=1;j<=m+1;j++) 
	    if (!((i+j)&1)) {
    	  if (j>1&&str[i][j-1]=='*'&&SETS::check(id(i,j),id(i+1,j-1))) {
    	  	  int u=cur[SETS::find_father(id(i,j))],v=cur[SETS::find_father(id(i+1,j-1))];
    	  	  a[u][u]=(a[u][u]+1)%MOD;
    	  	  a[v][v]=(a[v][v]+1)%MOD;
    	  	  a[u][v]=(a[u][v]-1+MOD)%MOD;
    	  	  a[v][u]=(a[v][u]-1+MOD)%MOD;
		  } 
		  if (j<=m&&str[i][j]=='*'&&SETS::check(id(i,j),id(i+1,j+1))) {
    	  	  int u=cur[SETS::find_father(id(i,j))],v=cur[SETS::find_father(id(i+1,j+1))];
    	  	  a[u][u]=(a[u][u]+1)%MOD;
    	  	  a[v][v]=(a[v][v]+1)%MOD;
    	  	  a[u][v]=(a[u][v]-1+MOD)%MOD;
    	  	  a[v][u]=(a[v][u]-1+MOD)%MOD;
		  } 
	    }
	ans=(ans+gause(sz))%MOD;
    }
  }
  printf("%d\n",ans);
  return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值