P3977 [TJOI2015]棋盘 题解

前制知识引导

状态压缩动态规划:P1896 [SCOI2005] 互不侵犯

矩阵加速优化:P1962 斐波那契数列


1.抓住数据范围的特点列出 dp 方程。

由于 \(m\le6\),很容易想到利用状态压缩解决问题。

用一个 \(6\) 位的二进制数存储每一行的状态,很容易能列出状态转移方程。

\(\mathit{dp}_{i,j}=\sum_{k=1}^{(1<<m)-1} (\mathit{dp}_{i-1,k})\),其中 \(k\)\(j\) 不能互相攻击。


2.找到合法方案

我们先得找到能使本行合理的二进制数。

例如一个棋子本行攻击模板为 \(1011\)\(k=2\)

那么有一个在第 \(0\) 列,距离 \(k\) 差两个单位距离。

那么等价于不能有两个 \(1\) 相隔距离为 \(2\)

所以当二进制数 \(j\) 满足 \((j\land(j<<2))=0\) 时合法。

由于棋子在模板中的第一行,所以处理不同行的两个二进制数要分从上往下和从下往上。

考虑暴力枚举。每遇到一个下方二进制数 \(j\)\(1\) 时,在上方进行枚举,
当遇到下方的 \(1\) 能攻击上方的 \(1\) 时,方案不合法。从上往下同理。

总复杂度 \(O(n \times 2^{m^2} )\)


3.矩阵快速幂优化

上面复杂度明显不能过掉本题,所以考虑优化。

对于每一次 dp 转移,每一个 \(j\) 都对应着固定的多个 \(k\),并且每次转移重复做着求和工作。

那么就可以考虑矩阵优化。

建立一个 \(m^2\times 1\) 的初始矩阵与一个 \(m^2\times m^2\) 的转移矩阵。

对于初始矩阵的每一位 \((j,1)\),如果 \(j\) 符合本行方案,那么赋值为 \(1\),否则赋为 \(0\)

对于转移矩阵的每一位 \((j,k)\),若 \(j\)\(k\) 互不侵犯,则赋为 \(1\),否则赋为 \(0\)

最后对于初始矩阵进行 \((n-1)\) 的重复求积,可以利用快速幂解决。

时间复杂度为 \(O(2^{m^3}\times \log(n))\)


4.细节处理

由于模数为 \(2^{32}\) 过大,需用龟速乘。

但毕竟已经允许用 __int128 了,所以就偷个懒吧。

#include<iostream>
#include<cstdio>
#define int __int128
using namespace std;
const int M=6;
int n,m,p,k,mod=1,t[5][10],vis[1<<M];
inline int read()
{
	char ch=getchar();
	int f=1,sum=0;
	while(ch>'9'||ch<'0')
	{
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		sum=(sum<<1)+(sum<<3)+(ch-48);
		ch=getchar();
	}
	return sum*f;
}
inline void write(int x)
{
	if(x<0)
	{
		putchar('-');
		x=-x;
	}
	if(x/10)write(x/10);
	putchar(x%10+'0');
}
void take_spc()
{
	for(int i=0;i<1<<m;i++)
	{
		vis[i]=1;
		for(int j=1;j<=p;j++)
		{
			if(t[2][j])
			{
				if(j<k&&(i&(i<<(k-j))))vis[i]=0;
				if(j>k&&(i&(i>>(j-k))))vis[i]=0;
			}
		}
	}
}
struct matrix
{
	int a[1<<M+1][1<<M+1],x,y;
	void init()
	{
		for(int i=1;i<=x;i++)
		{
			for(int j=1;j<=y;j++)
			{
				a[i][j]=0;
			}
		}
	}
}u,v;
matrix operator *(matrix fi,matrix se)
{
	matrix th;
	th.x=fi.x,th.y=se.y;
	th.init();
	for(int i=1;i<=fi.x;i++)
	{	
		for(int j=1;j<=se.y;j++)
		{	
			for(int p=1;p<=fi.y;p++)
			{
				th.a[i][j]+=fi.a[i][p]*se.a[p][j]%mod;
				th.a[i][j]%=mod;
			}
		}
	}
	return th;
}
bool check(int fi,int se)
{
	if(!vis[fi]||!vis[se])return false;
	for(int i=0;i<m;i++)
	{
		if(fi&(1<<i))
		{
			for(int j=i-k+2,pt=1;j<=i+p-k+1;j++,pt++)
			{
				if(j<1||j>m)continue;
				if(t[3][pt]&&((1<<(j-1))&se))return false;
			}
		}
	}
	for(int i=0;i<m;i++)
	{
		if(se&(1<<i))
		{
			for(int j=i-k+2,pt=1;j<=i+p-k+1;j++,pt++)
			{
				if(j<1||j>m)continue;
				if(t[1][pt]&&((1<<(j-1))&fi))return false;
			}
		}
	}
	return true;
}
void quick_pow(int num)
{
	while(num)
	{	
		
		if(num&1)v=u*v;
		u=u*u;
		num>>=1;
	}
}
signed main()
{
	for(int i=1;i<=32;i++)mod*=2;
	n=read(),m=read(),p=read(),k=read();
	k++;
	for(int i=1;i<=3;i++)for(int j=1;j<=p;j++)t[i][j]=read();
	take_spc();
	u.x=1<<m,u.y=1<<m,u.init();
	v.x=1<<m,v.y=1,v.init();
	u.x=1<<m,v.y=1;
	for(int i=0;i<1<<m;i++)if(vis[i])v.a[i+1][1]=1;
	for(int i=0;i<1<<m;i++)
	{
		for(int j=0;j<1<<m;j++)
		{
			if(check(i,j))u.a[i+1][j+1]=1;
		}
	}
	quick_pow(n-1);
	int ans=0;
	for(int i=1;i<=1<<m;i++)ans+=v.a[i][1],ans%=mod;
	write(ans);
	return 0;
}
posted @ 2023-02-24 13:15  Gmt丶Fu9ture  阅读(32)  评论(0)    收藏  举报