4000: [TJOI2015]棋盘
Time Limit: 10 Sec Memory Limit: 128 MB
Submit: 643 Solved: 314
[Submit][Status][Discuss]
Description
Input
输入数据的第一行为两个整数N,M表示棋盘大小。第二行为两个整数P,K,
表示攻击范围模板的大小,以及棋子在模板中的位置。接下来三行,
每行P个数,表示攻击范围的模版。每个数字后面一个空格。
Output
一个整数,表示可行方案Mod 2 ^32
Sample Input
2 2
3 1
0 1 0
1 1 1
0 1 0
Sample Output
7
HINT
1<=N<=10^6,1<=M<=6
这两天学习和代码的状态都很混乱,感觉是一个阵痛期。这题本身不难,当做一个模板记录题吧。
题意很显然(需要注意题目中的行列都是从0开始标号的)。注意到数据范围n很大而m很小,猜测出是dp,然后可以用1<<m个数字的二进制代表所有状态。由于每次状态转移方式固定不变,所以可以先预处理出转移矩阵,再用矩阵快速幂来进行加速。
预处理时,我们先判断两个状态本身是否合法(同一行不互相攻击),然后对合法的状态之间尝试连边,使得两个状态没有棋子互相攻击。
矩阵乘法时,答案应为AF^n的各个元素和,其中A是一个只有无棋子状态为1,其余状态为0的1(1<<m)的矩阵。为了方便,我们直接把第一行的和求出来就行了。
另外,题目里要求对1LL<<32取模,可以直接对unsigned溢出,进一步,可以直接对int溢出,输出时转换为unsigned即可,这是因为int是按照补码存储的,本质就是一个unsigned。
#include<cstdio>
#define bit(x,i) (x>>i-1&1)
using namespace std;
struct Matrix
{
int P,Q,M[65][65];
inline void operator = (const Matrix &t)
{
P=t.P;
Q=t.Q;
for(int i=1;i<=P;i++)
for(int j=1;j<=Q;j++)
M[i][j]=t.M[i][j];
}
void times(Matrix &a, Matrix &b)
{
if(a.Q!=b.P)
return ;
P=a.P;
Q=b.Q;
for(int i=1;i<=P;i++)
for(int j=1;j<=b.Q;j++)
{
M[i][j]=0;
for(int k=1;k<=a.Q;k++)
M[i][j]+=a.M[i][k]*b.M[k][j];
}
}
void quick_power(int t)
{
Matrix res[2],base[2];
int o=0,p=0;
res[p]=*this;
base[o]=*this;
--t;
while(t)
{
if(t&1)
{
res[p^1].times(res[p],base[o]);
p^=1;
}
base[o^1].times(base[o],base[o]);
o^=1;
t>>=1;
}
*this=res[p];
}
}G;
int n,m,p,k,A[4][7];
unsigned ans;
bool check_valid(int x)
{
for(int i=1;i<=m;i++) //枚举棋子对,看是否攻击
if(bit(x,i))
for(int j=1;j<=m;j++)
if(i!=j&&bit(x,j))
if(j-i+k>0&&j-i+k<=p&&A[2][j-i+k])
return false;
return true;
}
bool has_edge(int x, int y)
{
for(int i=1;i<=m;i++) //看上面是否攻击了下面
if(bit(x,i))
for(int j=1;j<=m;j++)
if(bit(y,j))
if(j-i+k>0&&j-i+k<=p&&A[3][j-i+k])
return false;
for(int i=1;i<=m;i++) //看下面是否攻击了上面
if(bit(y,i))
for(int j=1;j<=m;j++)
if(bit(x,j))
if(j-i+k>0&&j-i+k<=p&&A[1][j-i+k])
return false;
return true;
}
int main()
{
scanf("%d%d%d%d",&n,&m,&p,&k);
k++;
for(int i=1;i<=3;i++)
for(int j=1;j<=p;j++)
scanf("%d",&A[i][j]);
G.P=G.Q=1<<m;
for(int i=1;i<=(1<<m);i++)
if(check_valid(i-1))
for(int j=1;j<=(1<<m);j++)
if(check_valid(j-1))
G.M[i][j]=has_edge(i-1,j-1);
G.quick_power(n);
for(int i=1;i<=(1<<m);i++)
ans+=G.M[1][i];
printf("%u",ans);
return 0;
}