看不懂再来看本篇。
题目大意:
有个n行m列的棋盘,棋盘上可以放许多特殊的棋子。每个棋子的攻击范围是3行,p列。输入数据用一个3×p的矩阵给出了棋子攻击范围的模板,棋子被默认为模板中的第1行,第k列,则棋子能攻击到的位置是1,不能攻击到的位置是0。1≤p≤m,0≤k<p。输入数据保证第1行第k列的位置是1。打开门的密码就是,在要求棋子互相不能攻击到的前提下,摆放棋子的方案数。注意什么棋子都不摆放也算作一种可行方案。由于方案数可能很大,而密码为32位的二进制密码,所以ZJY仅需要知道方案数对2的32次方取余数的结果即可。
分析:
注意:题目太可恶了。这里的“第一行,第k列”都是从0开始计数的。也就是说,样例中的棋子位置其实是在正中央。也就是说,棋子所放位置一定在中间一行。
m极小,可以状压。
因为,n很大,直接朴素的一行一行处理一定会爆掉,所以必须想办法优化。
发现,其实每一行的方案数,只依赖上一行的放法,保证两行之间不会互相攻击即可。
当上一行的决策为i固定,这一行的决策j是否合法,是一个确定的事情。(i,j是压缩的二进制集合,(当然我们枚举的是dfs合法方案的编号,s[i],s[j]就是集合))
也就是说,不论第几行,i这个状态要么可以转化到下一行的j状态,要么不行,与第几行无关。
而且转移只依赖上一行的方案,所以可以写出一个固定的转移矩阵,base[i][j]为1表示,第i个集合的方案可以转化给j集合。
算法:dp,状压,矩阵快速幂优化
0.攻击矩阵压缩成二进制数三个。
1.dfs预处理所有中间排的符合集合
2.建矩阵,n^2枚举,相邻的符合条件的集合建关系,在矩阵中值就是1
3.ans:A*base^(n-1)
其中,A为初始化矩阵,因为第一行可以放下“所有的dfs找到的合法状态”。相当于是一个第一行都是1的矩阵。然后还剩n-1行,base^(n-1)
4.矩阵快速幂
注意:
1.mod较大,unsigned long long 可以防止爆掉,long long 就会爆了4个点。快速乘也行,但是直接慢了10倍,还得开O2.
2.各种细节,注意矩阵乘积顺序,没有交换律。base一定是乘矩阵,而不是被乘矩阵。
代码:(极丑)
#include<bits/stdc++.h> using namespace std; typedef unsigned long long ll; const int M=7; const int N=1e6+10; const long long mod=(ll)1<<32; int n,m; int s[70],cnt; int lim[3]; int p,k; ll base[70][70]; ll ans[70][70]; ll prin; void dfs(int now,int sum)//找单独一行的合法集合 { if(now==m+1) { int kk=sum;int tt=0; bool flag=true; while(kk) { tt++; if(kk&1) { if(tt<p-k) { int yy=lim[1]>>(p-k-tt);yy^=(1<<tt-1);//转移到棋子位置,并设定不能攻击到自己 if((yy&sum)!=0)//判断能否攻击到 { flag=false;break; } } else{ int yy=(lim[1]<<(tt-p+k)); yy^=(1<<tt-1);//判断能否攻击到 if((yy&sum)!=0) { flag=false;break; } } } kk/=2; } if(flag) s[++cnt]=sum;//可以,就新加一个编号,对应一个集合 return; } dfs(now+1,sum<<1|1); dfs(now+1,sum<<1); } void build() { for(int i=2;i<=cnt;i++) { for(int j=2;j<=cnt;j++)// i 上一行 j 这一行。 i能否转移到j { bool flag=true; int kk=s[j]; int tt=0; while(kk)//j能否攻击到i { tt++; if(kk&1) { if(tt<p-k) { int yy=lim[0]>>(p-k-tt); if((yy&s[i])!=0) { flag=false;break; } } else{ int yy=lim[0]<<(tt-p+k); if((yy&s[i])!=0) { flag=false;break; } } } kk/=2; } if(flag==false) continue; kk=s[i]; tt=0; while(kk)//i能否攻击到j { tt++; if(kk&1) { if(tt<p-k) { int yy=lim[2]>>(p-k-tt); if((yy&s[j])!=0) { flag=false;break; } } else{ int yy=lim[2]<<(tt-p+k); if((yy&s[j])!=0) { flag=false;break; } } } kk/=2; } if(flag==true)// i 可以转移到 j { base[i][j]=1; } } } for(int i=1;i<=cnt;i++) base[1][i]=1,base[i][1]=1;//因为s已经排过序,集合0一定是第一个,0可以转移到所有集合,所有集合也可以转移到0集合。 } void mul(ll a[70][70],ll b[70][70],ll c[70][70]) { ll ret[70][70]; memset(ret,0,sizeof ret); for(int i=1;i<=cnt;i++) for(int k=1;k<=cnt;k++) for(int j=1;j<=cnt;j++) { ret[i][j]=(ret[i][j]+a[i][k]*b[k][j]%mod)%mod; } memcpy(c,ret,sizeof ret); }//矩阵乘法 void qm(int y) { for(int i=1;i<=cnt;i++) ans[i][i]=1; while(y) { if(y&1) mul(ans,base,ans); mul(base,base,base); y>>=1; } }//快速幂 int main() { scanf("%lld%lld",&n,&m); scanf("%lld%lld",&p,&k); for(int i=0;i<3;i++) { int tot=0; int x; for(int j=1;j<=p;j++) { scanf("%lld",&x); tot|=x; if(j!=p) tot<<=1; } lim[i]=tot; } dfs(1,0); sort(s+1,s+cnt+1); build(); qm(n-1); ll A[70][70]; for(int i=1;i<=cnt;i++) A[1][i]=1;//初始矩阵(相当于dp初值) mul(A,ans,ans); for(int i=1;i<=cnt;i++) prin=(prin+ans[1][i]%mod)%mod; cout<<prin<<endl; return 0; }