我是fw确信
这种结论题拿头想???
没错我是fw
记RiR_iRi表示第iii行元素异或和,LiL_iLi表示第iii列元素异或和。那么矩阵全为零时显然Li,RiL_i,R_iLi,Ri全为零。
如果nnn,mmm都是偶数,那么每次操作(i,j)(i,j)(i,j)相当于把Lk(k≠i)L_k(k\ne i)Lk(k=i) 和 Rk(k≠j)R_k(k\ne j)Rk(k=j) 翻转。我们可以看成只对Li,RjL_i,R_jLi,Rj翻转,这又相当于只翻转(i,j)(i,j)(i,j)这一个点,因此任意010101矩阵都是可以还原的。
如果nnn是奇数,mmm是偶数,那么操作(i,j)(i,j)(i,j)相当于把Lk(k≠i)L_k(k\ne i)Lk(k=i)和所有RkR_kRk翻转,发现只和iii有关,结论是RkR_kRk必须全为000或全为111。
求方案数则对于每个右部点钦定一条边即可。
如果n,mn,mn,m都是奇数,那么操作(i,j)(i,j)(i,j)相当于把所有LkL_kLk和RkR_kRk翻转,于是结论很明显,必须初始全为000或全为111。
至于计数部分,如果(i,j)(i,j)(i,j)是问号就在二分图上连一条边,对于一个连通块要对每条边定0/10/10/1边权使点权满足奇偶性。可以考虑构建生成树,对于不在生成树上的边可以随便定,对于在生成树上的边的边权是唯一确定的。
我这个做法还是有点取巧了。想看数学分析的可以去看 Kubic的题解 。
毕竟我的线性代数没怎么搞懂。
代码咕了。
代码来了。如果这份代码有bug请联系博主(
#include
#define fi first
#define se second
#define ll long long
#define pb push_back
#define db double
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
const int mod=998244353;
int n,m,K,W,num,vis[4005],in[4005],L[2005],R[2005];
int V,E;
char s[2005][2005];
vectorg[4005];
ll fpow(ll x,ll y){ll z(1);for(;y;y>>=1){if(y&1)z=z*x%mod;x=x*x%mod;}return z;
}
void dfs(int u){vis[u]=num,V++;W^=(u<=n)?L[u]:R[u-n];for(auto v:g[u]){if(!vis[v]){dfs(v);}if(u<=n&&vis[v]==num)E++;}
}
ll solve(int f){ll res=1;for(int i=1;i<=n+m;i++)vis[i]=0;for(int i=1;i<=n+m;i++){if(!vis[i]){num++,V=E=W=0,dfs(i);if(W!=f*V%2)return 0;res=res*fpow(2,E-V+1)%mod;}}return res;
}
ll solve1(int f){int c=0;for(int i=1;i<=m;i++){if(R[i]!=f&&!in[n+i]){return 0;}if(in[n+i])c++;}return fpow(2,K-c);
}
ll solve2(int f){int c=0;for(int i=1;i<=n;i++){if(L[i]!=f&&!in[i]){return 0;}if(in[i])c++;}return fpow(2,K-c);
}
int main(){cin>>n>>m;for(int i=1;i<=n;i++){scanf("%s",s[i]+1);for(int j=1;j<=m;j++){if(s[i][j]=='1')L[i]^=1,R[j]^=1;if(s[i][j]=='?')in[i]++,in[n+j]++,K++,g[i].pb(n+j),g[n+j].pb(i);}}if(n%2==0&&m%2==0){cout<cout<<(solve1(0)+solve1(1))%mod;}else if(n%2==0&&m%2==1){cout<<(solve2(0)+solve2(1))%mod;}else {cout<<(solve(0)+solve(1))%mod;}
}