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);
 }
posted @ 2021-09-26 21:56  ·Iris  阅读(72)  评论(0编辑  收藏  举报