P3977 棋盘
看到数据范围中 \(m \leq 6\),可以想到这是个状压 dp 题。
如果你做过几道比较经典的状压 dp 题,比如炮兵阵地啥的。那你很自然的就能想到设出这样一个方程。
dp[i][S] 表示第 \(i\) 行棋子放置状态为 \(S\)。
\[dp[i][S] =\sum_{S' 和 S 互相不能攻击到,并且不能自己攻击自己} dp[i-1][S']
\]
复杂度是 \(O(n2^{2m})\)。
这显然是爆炸的,我们来尝试优化一下这个方程。
观察到复杂度很大程度上依赖 n,也是因为这个而爆炸的。所以我们尽量把这个 \(n\) 优化成 \(O(\sqrt n)\) 或者 \(O(\log n)\)。但平白无故造出一个根号显然很扯,所以向 \(\log\) 的方向来优化。
观察上面的暴力方程,似乎与 i 没有什么关系,那我们可以考虑矩阵加速优化方程。
具体来说,我们设矩阵 \(K\),\(K\) 的边长是 \(2^m\) 。如果对于两个状态 \(i\) 和 \(j\) 来说,如果 \(i\) 后面可以放置 \(j\),那么令 \(K[i][j] = 1\)。并且特殊处理 \(dp[1]\) 这一行所有的可行方案。
最后用矩阵快速幂优化一下,我的程序复杂度是 \(O(T^3\log n),T = 2^m\)
如果我没记错的话,好像是可以预处理矩阵幂次方,然后二进制拆分把复杂度优化到 \(O(T^2\log n)\)(还可以用 bitset 来处理快速幂再除个 64
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define INF 1ll<<30
template<typename _T>
inline void read(_T &x)
{
x=0;char s=getchar();int f=1;
while(s<'0'||s>'9') {f=1;if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
x*=f;
}
const int np = (1 << 6) + 10;
int n,m,p,k;
int la,now,nxt;
int q[np];
int dp[10][np];
int attack[np][np];
struct matrix{
unsigned a[1078][1078];
unsigned r,c;
inline void Memset()
{
memset(a,0,sizeof(a));
}
inline void Init(unsigned siz)
{
Memset();
r = c = siz;
for(unsigned i=0;i<siz;i++) a[i][i] = 1;
}
friend matrix operator *(const matrix &A,const matrix &B)
{
matrix q;
q.r = A.r;
q.c = B.c;
q.Memset();
for(unsigned i=0;i<A.r;i++)
{
for(unsigned j=0;j<B.c;j++)
{
if(!dp[1][j]) continue;
for(unsigned k(0);k<A.c;k++)
{
q.a[i][j] += A.a[i][k] * B.a[k][j];
}
}
}
return q;
}
inline void print()
{
for(unsigned i=0;i<r;i++)
{
for(unsigned j=0;j<c;j++)
{
cout<<a[i][j]<<" ";
}
printf("\n");
}
}
};
inline matrix mul(const matrix &A,const matrix &B)
{
matrix q;
q.r = A.r;
q.c = B.c;
q.Memset();
for(unsigned i=0;i<A.r;i++)
{
if(!dp[1][i]) continue;
for(unsigned j=0;j<B.c;j++)
{
if(!dp[1][j]) continue;
for(unsigned k(0);k<A.c;k++)
{
if(!dp[1][k]) continue;
q.a[i][j] += A.a[i][k] * B.a[k][j];
}
}
}
return q;
}
inline matrix power(matrix T,int b)
{
matrix res;
res.Init(1 << m);
while(b)
{
if(b & 1) res = mul(res,T);
T = mul(T,T);
b >>= 1;
}
return res;
}
inline bool Attack_(int a,int b)
{
int stan(0);
for(int i=0;i<m;i++)
{
if(!(1 << i & a)) continue;
if(i < k)
{
stan = nxt >> (k-i);
}
else
{
stan = nxt << (i-k);
}
if(stan & b) return false;
}
for(int i=0;i<m;i++)
{
if(!(1 << i & b)) continue;
if(i < k)
{
stan = la >> (k-i);
}
else
{
stan = la << (i-k);
}
if(stan & a) return false;
}
return true;
}
signed main()
{
read(n);read(m);
read(p);read(k);
for(int i=1;i<=3;i++)
{
int x = 0;
for(int j=0;j<p;j++) read(q[j]);
for(int j(0);j<p;j++) x += q[j] << j;
if(i == 1) la = x;
if(i == 2) now = x;
if(i == 3) nxt = x;
}
for(int i=0;i< 1 << m;i++)
{
for(int s = 0;s < 1 << m;s++)
{
if(Attack_(i,s)) attack[i][s] = 1;
}
}
for(int i=0;i < 1 << m;i ++)
{
int stan =0 ;
dp[1][i] = 1;
for(int q = 0;q < m;q ++ )
{
if(!(1 << q & i)) continue;
if(q < k) stan = now >> (k - q);
else stan = now << (q -k );
stan ^= 1 << q;
if((stan & i)) dp[1][i] = 0;
}
}
matrix T;
T.Memset();
T.r = T.c = 1 << m;
for(int i=0;i < 1 << m;i++)
{
for(int j=0;j < 1 << m;j ++)
{
T.a[i][j] = attack[i][j];
}
}
matrix op ;
op.Memset();
op.r = 1;
op.c = 1 << m;
for(int i =0 ;i<1 <<m;i++) op.a[0][i] = dp[1][i];
unsigned Ans(0);
T = power(T,n-1);
op = op * T;
for(unsigned i(0);i < 1<<m;i++)
{
int x = op.a[0][i];
Ans += x;
}
printf("%u",Ans);
}