P3977 [TJOI2015]棋盘 题解
题目传送门
一道结合了状压和矩阵快速幂的好题。
题目有一处bug就是这里的
废话不多说,首先观察到
然后,就考虑转移。对于每一行的每一种方案,都由上一行不会被下一行攻击到,并且不会攻击到下一行的方案转移而来。那么,对于第k行,有
其中
我们发现对于每一行,能转移的上一行方案其实是相同的,同时这个转移方程非常符合矩阵乘法的定义,所有考虑到用矩阵快速幂优化。
注意最后统计答案时,可以看作第
代码:
#include<bits/stdc++.h> #define usi unsigned int using namespace std; const int N = 1e6+100; inline int read(){ int x = 0; char ch = getchar(); while(ch<'0'||ch>'9'){ ch = getchar(); } while(ch>='0'&&ch<='9'){ x = x*10+ch-48; ch = getchar(); } return x; } int atk[4], po[4][80], tot;//攻击范围,每一种摆放方案及攻击范围,方案总数。 int n, m; int p, K; struct mat{ usi f[80][80]; int lth; mat(){ memset(f, 0, sizeof(f)); lth = tot; } usi * operator [](int x){return f[x];} mat operator*(mat B){ mat t, BT; for(int i = 0; i<lth; i++){ for(int j = 0; j<lth; j++){ BT[i][j] = B[j][i]; } } for(int i = 0; i<lth; i++){ for(int j = 0; j<lth; j++){ for(int l = 0; l<lth; l++){ t[i][j]+=f[i][l]*BT[j][l]; } } } return t; } void build(){ for(int i = 0; i<lth; i++){ f[i][i] = 1; } } void print(){ for(int i = 0; i<lth; i++, puts("")){ for(int j = 0; j<lth; j++){ printf("%d ", f[i][j]); } } } }; mat a, b; void dfs(int x, int pos, int y){ if(pos>=m){ po[1][++tot] = y; return; } if(pos<K){ if((((1<<pos)&x)==0)&&((((atk[1]>>(K-pos))&y)==0)))dfs(x+(atk[1]>>(K-pos)), pos+1, y+(1<<pos)); dfs(x, pos+1, y); } else{ if((((1<<pos)&x)==0)&&((((atk[1]<<(pos-K))&y)==0))) dfs(x+(atk[1]<<(pos-K)), pos+1, y+(1<<pos)); dfs(x, pos+1, y); } }//这样的搜索方式可以保证最后一种方案是空行。 inline mat fpow(mat x, int y){ mat tmp; tmp.build(); while(y){ if(y&1){ tmp = tmp*x; } x = x*x; y>>=1; } return tmp; } int main(){ n = read(), m = read(); p = read(), K = read(); for(int i = 0; i<3; i++){ for(int j = 0; j<p; j++){ int t = read(); atk[i]+=(1<<j)*t; } } atk[1]-=(1<<K);//注意我们钦定自己不会攻击自己。 int up = (1<<m); dfs(0, 0, 0); for(int i = 1; i<=tot; i++){ for(int j = 0; j<m; j++){ if((po[1][i]>>j)&1){ if(j<K){ po[0][i]|=(atk[0]>>(K-j)); po[2][i]|=(atk[2]>>(K-j)); } else{ po[0][i]|=(atk[0]<<(j-K)); po[2][i]|=(atk[2]<<(j-K)); } } } }//预处理各个情况的攻击范围,注意位运算要倒腾清。 for(int i = 1; i<=tot; i++){ for(int j = 1; j<=tot; j++){ if(((po[1][i]&po[2][j])==0)&&((po[0][i]&po[1][j]))==0){ a[i-1][j-1] = 1;//将合法的转移方案加入矩阵 } } } a.lth = tot; a = fpow(a, n); b[tot-1][tot-1] = 1; b.lth = tot; b = b*a; usi ans = 0; for(int i = 0; i<tot; i++){ ans+=b[tot-1][i]; }//统计答案 printf("%u\n", ans); return 0; }