• [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;
    }
  • 相关阅读:
    SpringDataJpa
    #pragma pack(n)的使用
    jquery中的ajax方法参数
    rapidjson的使用
    Linux下Qt安装
    jsoncpp 0.5 ARM移植
    GoAhead2.5移植到ARM教程
    Qt 4.7.2移植到ARM教程
    虚函数与纯虚函数的区别
    海康、大华IpCamera RTSP地址和格式
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9053593.html
Copyright © 2020-2023  润新知