[TJOI2015]棋盘

推荐:题解 P3977 【[TJOI2015]棋盘】

看不懂再来看本篇。

题目大意:

有个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;
}

 

posted @ 2018-05-17 22:07  *Miracle*  阅读(455)  评论(0编辑  收藏  举报