Description
给你一个n*m的网格图,你需要在这个网格图上选择若干条边,需要满足以下条件:
- 所有选出来的边需要构成若干个环,每条被选中的边必须在至少一个环内;
- 环与环只能存在交点,不能存在交边;
同时网格图上每个格子还有一个权值 a [ i ] [ j ] = 0 / 1 / − 1 a[i][j]=0/1/-1 a[i][j]=0/1/−1,表示与这个格子相邻的4条边中,必须要有偶数个或者奇数个的边被选中,-1表示无限制。
求所有选边的方案数,mod 998244353,
2
<
=
n
,
m
<
=
17
2<=n,m<=17
2<=n,m<=17
Solution
一眼看上去怎么看都是插头DP,
结果正解是高斯消元你敢信???
因为必须要构成环,我们考虑用格子的选中状态来表示边的选中状态,
即,我们选择若干个格子,一堆联通的格子的外轮廓就是我们选择的边,这样可以表示出所有可能的选法。
转化为这样,题目就很好做了。
某一条边是被选中的,当且仅当这条边相邻的两个格子恰好被选中一个,
于是我们就可以列出许多的异或方程,用高斯消元找自由元个数即可。
答案一定是2的次幂。
复杂度: O ( ( n m ) 3 / 32 ) O((nm)^3/32) O((nm)3/32)
Code
#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define JS(i,j) ((i)*m-m+(j))
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
const int N=20,mo=998244353;
int read(int &n)
{
bool q=0;n=0;char ch=' ';
for(;ch!='-'&&(ch<'0'||ch>'9');ch=getchar());
if(ch=='-')ch=getchar(),q=1;
for(;ch>='0'&&ch<='9';ch=getchar())n=(n<<3)+(n<<1)+ch-48;
return q?n=-n:n;
}
int n,m,ans;
int a[N][N];
bitset<N*N>b[N*N];
int b0;
int CAL(int n,int m)
{
int la=1;
fo(I,1,m)
{
int q=la;
for(;q<=n&& !b[q][I];++q);
if(q>n)continue;
if(q>la)
{
swap(b[la],b[q]);
}
fo(i,la+1,n)if(b[i][I])
{
b[i]^=b[la];
}
++la;
}
int ans=0;
fo(i,1,n)if(b[i].count())
{
++ans;
if(b[i].count()==1 && b[i][0]==1)return -1;
}
return m-ans;
}
int main()
{
freopen("!.in","r",stdin);
freopen("1.out","w",stdout);
int q,w,_;
read(_);
while(_--)
{
read(n),read(m);
--n,--m;
fo(i,1,n)
{
char ch=' ';
for(;ch!='.'&&(ch<'0'||ch>'9');ch=getchar());
fo(j,1,m)
{
if(ch=='0')a[i][j]=2;
else if(ch=='1')a[i][j]=1;
else a[i][j]=0;
ch=getchar();
}
}
b0=0;
fo(i,1,n)fo(j,1,m)if(a[i][j])
{
++b0;
b[b0][0]=a[i][j]%2;
if(i>1)b[b0][JS(i-1,j)]=1;
if(i<n)b[b0][JS(i+1,j)]=1;
if(j>1)b[b0][JS(i,j-1)]=1;
if(j<m)b[b0][JS(i,j+1)]=1;
}
ans=CAL(b0,n*m);
if(ans<0)printf("0\n");
else {
LL t=1;
for(;ans;--ans)t=(t<<1)%mo;
printf("%lld\n",t);
}
fo(i,1,b0)b[i].reset();
}
return 0;
}