bzoj 4000: [TJOI2015]棋盘

矩乘,题意坑爹,那个第一行是中间那一行,所以记录一行的状态就可以了。f[S]表示最后一行状态为S的方案数,然后暴力看能否转移。
    
    
    
    
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
 
#define ll long long
#define ui unsigned int
#define inf 1e9
#define eps 1e-8
#define md
#define N 70
using namespace std;
int m;
bool c[20],d[20];
int list[3][20];
bool safe[N];
struct Ju
{
ui a[N][N];
int x,y;
} a,ju,mid,ans;
bool operator *= (Ju &a,Ju &b)
{
memset(mid.a,0,sizeof(mid.a));
mid.x=a.x; mid.y=b.y;
for (int i=0;i<a.x;i++)
for (int k=0;k<a.y;k++)
if (a.a[i][k])
for (int j=0;j<b.y;j++)
mid.a[i][j]+=a.a[i][k]*b.a[k][j];
a=mid;
}
 
void kpow(Ju &a,int b)
{
memset(ans.a,0,sizeof(ans.a));
ans.x=a.x; ans.y=a.y;
for (int i=0;i<a.x;i++) ans.a[i][i]=1;
while (b)
{
if (b&1) ans*=a;
a*=a; b>>=1;
}
a=ans;
}
 
bool ok(bool a[],bool b[],int c[])
{
for (int i=0;i<m;i++)
{
if (!a[i]) continue;
for (int j=1;j<=c[0];j++)
{
int t=i+c[j];
if (t>=0&&t<m&&b[t]) return 0;
}
}
return 1;
}
 
bool get_safe(int S)
{
for (int i=0;i<m;i++) c[i]=(S&(1<<i))>0;
return ok(c,c,list[1]);
}
 
bool check(int s1,int s2)
{
if (!safe[s1]||!safe[s2]) return 0;
for (int i=0;i<m;i++) c[i]=(s1&(1<<i))>0,d[i]=(s2&(1<<i))>0;
return ok(c,d,list[2])&&ok(d,c,list[0]);
}
 
int main()
{
int n,P,K;
scanf("%d%d",&n,&m);
scanf("%d%d",&P,&K);
for (int i=0;i<3;i++)
for (int j=0;j<P;j++)
{
int x;
scanf("%d",&x);
if (x&&!(i==1&&j==K)) list[i][++list[i][0]]=j-K;
}
int S=(1<<m);
for (int i=0;i<S;i++) safe[i]=get_safe(i);
ju.x=ju.y=S;
for (int i=0;i<S;i++)
for (int j=0;j<S;j++)
ju.a[i][j]=check(i,j);
kpow(ju,n);
a.x=1; a.y=S; a.a[0][0]=1; a*=ju;
ui ans=0;
for (int i=0;i<S;i++) ans+=a.a[0][i];
printf("%u\n",ans);
return 0;
}


你可能感兴趣的:(bzoj 4000: [TJOI2015]棋盘)