POJ1038

273 篇文章 0 订阅
112 篇文章 0 订阅

POJ1038Bugs Integrated, Inc.

现在给你一个N*M的矩阵,然后矩阵有K个坏了的格子,矩阵中黑格子表示坏的格子,要你在所有好的格子中选出尽量多的2*3(或3*2)小矩阵,问你最多能选多少个这样的小矩阵.


输入:首先是一个t,(1<=t<=5)表示实例个数.对于每个实例,第一行是N,M和K,N (1<= N <= 150), M (1 <= M <= 10), K (0 <= K <= MN).接下来K行每行一对坐标x和y,表示(x,y)格是坏的.

输出:输出最多能选多少个2*3小矩阵.

分析:由题意我们可知假设从上到下开始摆放2*3(或3*2,后文统称为小矩阵)矩阵,那么当我们放到第i行的时候(我们只以当前格作为小矩阵的右下角摆放),当前行如何摆放只和第i-1和i-2行的摆放方式有关.

对于n*m矩阵,我们把已经摆放了小矩阵的格子填1,没摆放的填2.令d[i][S1][S2]=x,表示当前第i行的填充状态为S1,第i-1行的填充状态为S2时,此时前i行能放最多小矩阵数目为x个.

d[i+1][S1][S2]=max{d[i][S3][S4]+ get(i+1,S3,S4)},尝试在第i+1行每个格子放下向左或向上的小矩阵,生成新的S1和S2状态以及对应的格子数目.其中S2状态是由S3状态生成的.get(i+1,S3,S4)表示从d[i][S3][S4]生成d[i+1][S1][S2]最多能新摆放多少个小矩阵.

初值是:d[1][S1][S2]=x,初值通过模拟摆放方式计算出来.其他d值初始化为-1,标记未计算状态.

代码:测试结果正确,速度也够快但是内存溢出

//测试了多组数据结果都正确速度也够快的程序,但是内存要求太多溢出
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define base(i,j) base[i][j]
int n,m,bad,cnt;
int d[155][1<<10][1<<10];
int g[155][11];//保存初始矩阵
int state[300];//该state[i]=s保存的是一个三进制数,s的第i位为0表示不放,第i位为1表示左方,第i位为2表示上方
int mi[12];//幂运算
int base[60000][12];//base[x][i]=2,x的三进制形式的第i位是2
int sum;//记录最大能摆放的数目
 
char s_2[15];//用于打印数的三进制形式测试中间结果
char *print_2(int x)//保存x的二进制形式到s_2中,且x的第0位对应第0列,对应s_2中的第0位中.
{
    for(inti=0;i<15;i++)
       s_2[i]='0';
   int p=0;
   while(x)
    {
       s_2[p++]='0'+x%2;//x的第0位对应第0列,保存在s_2的第0位中
       x/=2;
    }
   s_2[m]='\0';
   return s_2;
}
char s_3[15];//用于打印数的三进制形式测试中间结果
char *print_3(int x)//保存x的三进制形式到s_3中,且x的第0位对应第0列,对应s_3中的第0位中.
{
   for(int i=0;i<15;i++)
       s_3[i]='0';
   int p=0;
   while(x)
    {
       s_3[p++]='0'+x%3;//x的第0位对应第0列,保存在s_3的第0位中
       x/=3;
    }
   s_3[m]='\0';
   return s_3;
}
 
inline bool good(int S)//判断状态S(三进制)是否合理,即在m个格子都是好格的情况下,能出现S状态的摆放
                 //s的第i位为0表示不放,第i位为1表示左方,第i位为2表示上方
{
   for(int i=0;i<m;i++)//依次判断S的三进制中的每一位是1还是2就行
    {
       if(base[S][i]==1)//放的左矩阵
       {
           if(i<=1)//第1个和0个格子放不下左矩阵
                return false;
           if( base[S][i-1]!=0||base[S][i-2]!=0 )//此时i>=2,但是他的前两个格子已放
                return false;
       }
       else if(base[S][i]==2)//放的上矩阵
       {
           if(i<=0)//第0个格子放不下上矩阵
                return false;
           if( base[S][i-1]!=0  )//此时i>=1,但是他的前一个格子已放
                return false;
       }
    }
   return true;
}
bool no_up(int S)//S的三进制形式表示的摆放方式没有任何上放
{
   for(int i=0;i<m;i++)
       if(base[S][i]==2)
           return false;
   return true;
}
inline bool legal(int S,int r,int S3,intS4)//要求r>=1判断S的三进制摆放状态在第r行且以S3和S4的二进制填充条件下是否合法
{//关于S3和S4是否分别满足如r-1行和r-2行给的初始矩阵,我们不管,我们假设他们已经满足了
 //但是要注意当按照S摆放之后 生成的S1和S2要符合r和r-1行的初始矩阵
 
   for(int i=0;i<m;i++)
    {
 
       if(base[S][i]==1)//此格左放,要考虑它的前两列格子和上一行格子,还有它本身
       {
            if(g[r][i]==0 || g[r][i-1]==0 ||g[r][i-2]==0)//r行的3列不合法
                return false;
 
           if(r<=0 || g[r-1][i]==0 || g[r-1][i-1]==0 || g[r-1][i-2]==0)//r-1行的3列不合法
                return false;
 
           if( ( S3&(1<<i) ) || ( S3&(1<< (i-1) ) ) || (S3&(1<< (i-2) ) )  )//上一行兼容
                return false;
 
       }
       if(base[S][i]==2)//此格上放,要考虑它的前一列和上两行,包括它本身
       {
           if(g[r][i]==0 || g[r][i-1]==0)//r行的2列不合法
                return false;
           if(r<=0 || g[r-1][i]==0 || g[r-1][i-1]==0)//r-1行的2列不合法
                return false;
           if(r<=1 || g[r-2][i]==0 || g[r-2][i-1]==0)//r-2行的2列不合法
                return false;
           if( ( S3&(1<<i) ) || ( S3&(1<< (i-1) ) ) || (S4&(1<< (i) ) ) || ( S4&(1<< (i-1) ) ) )
           //上两行兼容
                return false;
       }
 
    }
 
 
 
   return true;
}
void play(int r,int S3,int S4)//尝试摆放第r行(第r-1行和r-2行状态为S3和S4),并更新d[r][S1][S2]
{
   for(int i=0;i<cnt;i++)if( legal(state[i],r,S3,S4) )//对于每一种预先生成的合理且合法状态
    {
       int S1=0,S2=S3;
       int num=0;//记录当前行新摆放了几个小矩阵
       for(int j=0;j<m;j++)//根据state[i]的三进制摆放状态生成当前行和上一行的状态
       {
           if(base[state[i]][j]==1)//第j列左放
           {
                S1 =S1|(1<<j)|(1<<(j-1) )|(1<<(j-2) );
                S2 =S2|(1<<j)|(1<<(j-1) )|(1<<(j-2) );
                num++;
           }
           else if(base[state[i]][j]==2)//第j列上放,不用更新第r-2行的S4,因为我们的d状态只需要用到前两行
           {
                S1 =S1|(1<<j)|(1<<(j-1) );
                S2 = S2|(1<<j)|(1<<(j-1));
                num++;
           }
       }
       d[r][S1][S2]=max(d[r][S1][S2],d[r-1][S3][S4]+num);
 
       //printf("d[%d][%s][%s]=%d  摆放:%s",r,print_2(S1),print_2(S2),d[r][S1][S2],print_3(state[i]));
       //printf("  S3=%s S4=%s\n",print_2(S3),print_2(S4));
       //printf("d[%d][%s][%s]=%d",r,print_2(S1),print_2(S2),d[r][S1][S2]);
       //printf("当前选中的摆放状态为state[%d]=%s,且state_num[%d]为%d ",i,print_3(state[i]),i,state_num[i]);
       //if(d[r][S1][S2] == d[r-1][S3][S4]+state_num[i])
         //  printf("  由 d[%d][%s][%s]=%d 生成\n",r-1,print_2(S3),print_2(S4),d[r-1][S3][S4]);
       //else
         //  printf("不由 d[%d][%s][%s]=%d 生成\n",r-1,print_2(S3),print_2(S4),d[r-1][S3][S4]);
 
       if(r==n-1) sum = max(sum , d[r][S1][S2]);//取得最大值
    }
}
void play_1()//更新d的第1行和第2行状态,即d的初始状态,行号从0开始计数.
{
   int S3=0;//放第1行前 第0行的填充状态
   int S4=(1<<m)-1;//放第1行前 第-1行的填充状态为全1
   int r=1;//行号
   for(int i=0;i<cnt;i++)if( legal(state[i],r,S3,S4)  )//对于每一种预先生成的合理(good)且合法(legal)且与S3S4(legal)兼容三进制摆放状态
   {//no_up()用来限制第一行的摆放不会出现任何上放的情况
       int S1=0,S2=S3;
       int num=0;//记录当前行新摆放了几个小矩阵
       for(int j=0;j<m;j++)//根据state[i]的三进制摆放状态生成当前行和上一行的状态
       {
           if(base[state[i]][j]==1)//在第1行的第j列只能左放不能上放
           {
                S1 =S1|(1<<j)|(1<<(j-1) )|(1<<(j-2) );
                S2 =S2|(1<<j)|(1<<(j-1) )|(1<<(j-2) );
                num++;
           }
           /*
           else if(base[state[i]][j]==2)//在第j列上放,不用更新第r-2行的S4,因为我们的d状态只需要用到前两行
           {
                S1 =S1|(1<<j)|(1<<(j-1) );
                S2 =S2|(1<<j)|(1<<(j-1) );
           }
           */
       }
       d[r][S1][S2]=max(d[r][S1][S2],num);
       //printf("d[%d][%s][%s]=%d\n",r,print_2(S1),print_2(S2),d[r][S1][S2]);
 
       if(n==2) sum = max(sum , d[r][S1][S2]);//如果只有两行,那么这就是最大值
    }
}
 
int main()
{
   //freopen("out.txt", "w", stdout);
   int temp=1;
   for(int i=0;i<=10;i++)
    {
       mi[i]=temp;
       temp*=3;
    }
   for(int value=0;value<mi[10];value++)
    {
       temp = value;
       int p =0;
       while(temp>0)
       {
           base[value][p++]=temp%3;
           temp/=3;
       }
    }
    intT;
   while(scanf("%d",&T)==1&&T)
    {
       while(T--)
       {
           sum = 0;//最大值初始化
           scanf("%d%d%d",&n,&m,&bad);
           if(n==1)//只有一行时特殊处理
           {
                printf("0\n");
                continue;
           }
           for(int i=0;i<n;i++)//此处可以改成 memset(g,1,sizeof(g)); 吗?
                for(int j=0;j<m;j++)
                    g[i][j]=1;
           for(int i=0;i<bad;i++)
           {
                int a,b;
               scanf("%d%d",&a,&b);
                a--;b--;
                g[a][b]=0;//坏格子标记为1
           }
           cnt = 0;
           for(int S=0;S<mi[m];S++)//预先生成所有合理的三进制摆放状态,在m个格子都是好格子的时候能放下就行
           {
                if(good(S))
                {
                    state[cnt++]=S;
 
                }
           }
           //printf("m=%d cnt=%d\n",m,cnt);
           memset(d,-1,sizeof(d));
           play_1();//生成d[1][][]的初始值,即第一行的d所有可能的值,行数从0开始计数
           for(int r=2;r<n;r++)
           {
                for(intS3=0;S3<(1<<m);S3++)//S3的二进制形式是第r-1行的填充状态
                {
                    for(intS4=0;S4<(1<<m);S4++)//S4的二进制形式是第r-2行的填充状态
                    {
                       if(d[r-1][S3][S4]==-1)//(r-1,S3,S4)是一个非法的不可生成 的状态
                            continue;
                        play(r,S3,S4);
                    }
                }
           }
           printf("%d\n",sum);
       }
    }
   return 0;
}


 

第二份代码:超时 但是我测试的速度还可以,这份代码使用滚动数组把需要的内存缩小了很多,但是还是超时.

 

//测试了多组数据结果都正确速度也够快的程序,但是内存要求太多溢出
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int cur_r;
int n,m,bad,cnt;
int d[2][1<<10][1<<10];
int g[155][11];//保存初始矩阵
int state[300];//该state[i]=s保存的是一个三进制数,s的第i位为0表示不放,第i位为1表示左方,第i位为2表示上方
int mi[12];//幂运算
//int base[60000][12];//base[x][i]=2,x的三进制形式的第i位是2
int sum;//记录最大能摆放的数目
int base(int S,int i)//得到状态S的三进制形式的第i位
{
   for(int j=0;j<i;j++)
    {
       S=S/3;
    }
   return  S%3;
}
char s_2[15];//用于打印数的三进制形式测试中间结果
char *print_2(int x)//保存x的二进制形式到s_2中,且x的第0位对应第0列,对应s_2中的第0位中.
{
   for(int i=0;i<15;i++)
       s_2[i]='0';
   int p=0;
   while(x)
    {
       s_2[p++]='0'+x%2;//x的第0位对应第0列,保存在s_2的第0位中
       x/=2;
    }
   s_2[m]='\0';
   return s_2;
}
 
char s_3[15];//用于打印数的三进制形式测试中间结果
char *print_3(int x)//保存x的三进制形式到s_3中,且x的第0位对应第0列,对应s_3中的第0位中.
{
   for(int i=0;i<15;i++)
       s_3[i]='0';
   int p=0;
   while(x)
    {
       s_3[p++]='0'+x%3;//x的第0位对应第0列,保存在s_3的第0位中
       x/=3;
    }
   s_3[m]='\0';
   return s_3;
}
 
inline bool good(int S)//判断状态S(三进制)是否合理,即在m个格子都是好格的情况下,能出现S状态的摆放
                 //s的第i位为0表示不放,第i位为1表示左方,第i位为2表示上方
{
   for(int i=0;i<m;i++)//依次判断S的三进制中的每一位是1还是2就行
    {
       if(base(S,i)==1)//放的左矩阵
       {
           if(i<=1)//第1个和0个格子放不下左矩阵
                return false;
           if( base(S,i-1)!=0||base(S,i-2)!=0 )//此时i>=2,但是他的前两个格子已放
                return false;
       }
       else if(base(S,i)==2)//放的上矩阵
       {
           if(i<=0)//第0个格子放不下上矩阵
                return false;
           if( base(S,i-1)!=0  )//此时i>=1,但是他的前一个格子已放
                return false;
       }
    }
   return true;
}
bool no_up(int S)//S的三进制形式表示的摆放方式没有任何上放
{
   for(int i=0;i<m;i++)
       if(base(S,i)==2)
           return false;
   return true;
}
inline bool legal(int S,int r,int S3,intS4)//要求r>=1判断S的三进制摆放状态在第r行且以S3和S4的二进制填充条件下是否合法
{//关于S3和S4是否分别满足如r-1行和r-2行给的初始矩阵,我们不管,我们假设他们已经满足了
 //但是要注意当按照S摆放之后 生成的S1和S2要符合r和r-1行的初始矩阵
 
   for(int i=0;i<m;i++)
    {
 
       if(base(S,i)==1)//此格左放,要考虑它的前两列格子和上一行格子,还有它本身
       {
           if(g[r][i]==0 || g[r][i-1]==0 || g[r][i-2]==0)//r行的3列不合法
                return false;
 
           if(r<=0 || g[r-1][i]==0 || g[r-1][i-1]==0 || g[r-1][i-2]==0)//r-1行的3列不合法
                return false;
 
           if( ( S3&(1<<i) ) || ( S3&(1<< (i-1) ) ) || (S3&(1<< (i-2) ) )  )//上一行兼容
                return false;
 
       }
       if(base(S,i)==2)//此格上放,要考虑它的前一列和上两行,包括它本身
       {
           if(g[r][i]==0 || g[r][i-1]==0)//r行的2列不合法
                return false;
           if(r<=0 || g[r-1][i]==0 || g[r-1][i-1]==0)//r-1行的2列不合法
                return false;
           if(r<=1 || g[r-2][i]==0 || g[r-2][i-1]==0)//r-2行的2列不合法
               return false;
           if( ( S3&(1<<i) ) || ( S3&(1<< (i-1) ) ) || (S4&(1<< (i) ) ) || ( S4&(1<< (i-1) ) ) )
           //上两行兼容
                return false;
       }
    }
   return true;
}
void play(int r,int S3,int S4)//尝试摆放第r行(第r-1行和r-2行状态为S3和S4),并更新d[r][S1][S2]
{
   for(int i=0;i<cnt;i++)if( legal(state[i],r,S3,S4) )//对于每一种预先生成的合理且合法状态
    {
       int S1=0,S2=S3;
       int num=0;//记录当前行新摆放了几个小矩阵
       for(int j=0;j<m;j++)//根据state[i]的三进制摆放状态生成当前行和上一行的状态
       {
           if(base(state[i],j)==1)//第j列左放
           {
                S1 =S1|(1<<j)|(1<<(j-1) )|(1<<(j-2) );
                S2 =S2|(1<<j)|(1<<(j-1) )|(1<<(j-2) );
                num++;
           }
           else if(base(state[i],j)==2)//第j列上放,不用更新第r-2行的S4,因为我们的d状态只需要用到前两行
           {
                S1 =S1|(1<<j)|(1<<(j-1) );
                S2 =S2|(1<<j)|(1<<(j-1) );
                num++;
           }
       }
       d[cur_r][S1][S2]=max(d[cur_r][S1][S2],d[1-cur_r][S3][S4]+num);
 
       //printf("d[%d][%s][%s]=%d  摆放:%s",r,print_2(S1),print_2(S2),d[r][S1][S2],print_3(state[i]));
       //printf("  S3=%sS4=%s\n",print_2(S3),print_2(S4));
       //printf("d[%d][%s][%s]=%d",r,print_2(S1),print_2(S2),d[r][S1][S2]);
       //printf("当前选中的摆放状态为state[%d]=%s,且state_num[%d]为%d ",i,print_3(state[i]),i,state_num[i]);
       //if(d[r][S1][S2] == d[r-1][S3][S4]+state_num[i])
         //  printf("  由 d[%d][%s][%s]=%d 生成\n",r-1,print_2(S3),print_2(S4),d[r-1][S3][S4]);
       //else
         //  printf("不由 d[%d][%s][%s]=%d 生成\n",r-1,print_2(S3),print_2(S4),d[r-1][S3][S4]);
 
       if(r==n-1) sum = max(sum , d[cur_r][S1][S2]);//取得最大值
    }
}
void play_1()//更新d的第1行和第2行状态,即d的初始状态,行号从0开始计数.
{
   int S3=0;//放第1行前 第0行的填充状态
   int S4=(1<<m)-1;//放第1行前 第-1行的填充状态为全1
   int r=1;//行号
   for(int i=0;i<cnt;i++)if( legal(state[i],r,S3,S4)  )//对于每一种预先生成的合理(good)且合法(legal)且与S3S4(legal)兼容三进制摆放状态
   {//no_up()用来限制第一行的摆放不会出现任何上放的情况
       int S1=0,S2=S3;
       int num=0;//记录当前行新摆放了几个小矩阵
       for(int j=0;j<m;j++)//根据state[i]的三进制摆放状态生成当前行和上一行的状态
       {
           if(base(state[i],j)==1)//在第1行的第j列只能左放不能上放
           {
                S1 =S1|(1<<j)|(1<<(j-1) )|(1<<(j-2) );
                S2 =S2|(1<<j)|(1<<(j-1) )|(1<<(j-2) );
                num++;
           }
           /*
           else if(base[state[i]][j]==2)//在第j列上放,不用更新第r-2行的S4,因为我们的d状态只需要用到前两行
           {
                S1 =S1|(1<<j)|(1<<(j-1) );
                S2 =S2|(1<<j)|(1<<(j-1) );
           }
           */
       }
       d[r][S1][S2]=max(d[r][S1][S2],num);
       //printf("d[%d][%s][%s]=%d\n",r,print_2(S1),print_2(S2),d[r][S1][S2]);
 
       if(n==2) sum = max(sum , d[r][S1][S2]);//如果只有两行,那么这就是最大值
    }
}
 
int main()
{
   //freopen("out.txt", "w", stdout);
   int temp=1;
   for(int i=0;i<=10;i++)
    {
       mi[i]=temp;
       temp*=3;
    }
   /*
   for(int value=0;value<mi[10];value++)
    {
       temp = value;
       int p =0;
       while(temp>0)
       {
           base[value][p++]=temp%3;
           temp/=3;
       }
    }
   */
   int T;
   while(scanf("%d",&T)==1&&T)
    {
       while(T--)
       {
           sum = 0;//最大值初始化
           scanf("%d%d%d",&n,&m,&bad);
           if(n==1)//只有一行时特殊处理
           {
                printf("0\n");
                continue;
           }
           for(int i=0;i<n;i++)//此处可以改成 memset(g,1,sizeof(g)); 吗?
                for(int j=0;j<m;j++)
                    g[i][j]=1;
            for(int i=0;i<bad;i++)
           {
                int a,b;
               scanf("%d%d",&a,&b);
                a--;b--;
                g[a][b]=0;//坏格子标记为1
           }
           cnt = 0;
           for(int S=0;S<mi[m];S++)//预先生成所有合理的三进制摆放状态,在m个格子都是好格子的时候能放下就行
           {
                if(good(S))
                {
                    state[cnt++]=S;
 
                }
           }
           //printf("m=%d cnt=%d\n",m,cnt);
           memset(d,-1,sizeof(d));
           play_1();//生成d[1][][]的初始值,即第一行的d所有可能的值,行数从0开始计数
           cur_r=1;
           for(int r=2;r<n;r++)
           {
                cur_r = 1-cur_r;
                for(intS3=0;S3<(1<<m);S3++)//S3的二进制形式是第r-1行的填充状态
                {
                    for(intS4=0;S4<(1<<m);S4++)//S4的二进制形式是第r-2行的填充状态
                    {
                       if(d[1-cur_r][S3][S4]==-1)//(r-1,S3,S4)是一个非法的不可生成 的状态
                            continue;
                        play(r,S3,S4);
                    }
                }
           }
           printf("%d\n",sum);
       }
    }
   return 0;
}

通过分析,使用轮廓线动态规划来算和上面的二行DP复杂度基本一样,估计还是超时.

新的解法:

通过分析我们知道当前第i行能不能放左上矩阵只与第i-1行和第i-2行有关,且如果第i-1行是坏格或者被占用的话即使第i-2行的对应格子是空的也没用.所以我们归纳下面3种表示状态:

1. 第(i-1,j)格空闲,第(i-2,j)格空闲,此两个表示一个0

2. 第(i-1,j)格空闲,第(i-2,j)格占用,此两个表示一个1

3. 第(i-1,j)格占用,此格和第(i-2,j)格表示一个2

则我们用一个三进制数就可以表示前两行的所有状态了.

占用

空闲

空闲

占用

空闲

占用

空闲

占用

上面两行的状态用一个三进制数表示就是S=2021,此数十进制为31.注意S的三进制形式的第0位数字表示的是矩阵第0列的状态而不是最后一列.即S中的1表示的是矩阵第一列(空闲,占用)的状态.

令d[cur][S]=x表示当前行以及前一行的状态是S时,最多能摆x个小矩阵.状态转移方程是:

d[cur][S]= max{ d[1-cur][S1] } 其中1-cur表示上一列生成的状态,cur表示当前列处理的状态,并且状态S是可以通过摆放当前行来将S1的下一行加上当前行转化为S的.

初值:d[0][<11111>]=0.

代码:测试几组结果正确,但是依然超时.

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,bad,cnt,sum,cur;
int d[2][60000];
int g[155][11];
int mi[12];//幂运算
int base[60000][12];//base[x][i]=2,x的三进制形式的第i位是2
int state[300];//该state[i]=s保存的是一个三进制数,s的第i位为0表示不放,第i位为1表示左方,第i位为2表示上方
 
inline bool good(int S)//判断状态S(三进制)是否合理,即在m个格子都是好格的情况下,能出现S状态的摆放
//s的第i位为0表示不放,第i位为1表示左方,第i位为2表示上方
{
   for(int i=0; i<m; i++) //依次判断S的三进制中的每一位是1还是2就行
    {
       if(base[S][i]==1)//放的左矩阵
       {
           if(i<=1)//第1个和0个格子放不下左矩阵
                return false;
           if( base[S][i-1]!=0||base[S][i-2]!=0 )//此时i>=2,但是他的前两个格子已放
                return false;
       }
       else if(base[S][i]==2)//放的上矩阵
       {
           if(i<=0)//第0个格子放不下上矩阵
                return false;
           if( base[S][i-1]!=0  )//此时i>=1,但是他的前一个格子已放
                return false;
       }
    }
   return true;
}
bool legal(int r,int S,int S2)//判断摆放方式S是否和g[r]且和前两行的联合状态S2都兼容,S在这里是摆放状态不是联合状态
{
   //关于S2是否分别满足如r-1行和r-2行给的初始矩阵,我们不管,我们假设他已经满足了
   //但是要注意当按照S摆放之后 生成的S1联合状态要符合r和r-1行的初始矩阵
 
   for(int i=0; i<m; i++) //第i列
    {
       if(base[S][i]==1)//此格左放,要考虑它的前两列格子和上一行格子,还有它本身
       {
           if(g[r][i]==0 || g[r][i-1]==0 || g[r][i-2]==0)//r行的3列不合法
                return false;
           if(r<=0 || g[r-1][i]==0 || g[r-1][i-1]==0 || g[r-1][i-2]==0)//r-1行的3列不合法
                return false;
           if( base[S2][i]==2 || base[S2][i-1]==2 || base[S2][i-2]==2  )//联合状态第i,i-1,i-2位至少有一位是2
                return false;
       }
       if(base[S][i]==2)//此格上放,要考虑它的前一列和上两行,包括它本身
        {
           if(g[r][i]==0 || g[r][i-1]==0)//r行的2列不合法
                return false;
           if(r<=0 || g[r-1][i]==0 || g[r-1][i-1]==0)//r-1行的2列不合法
                return false;
           if(r<=1 || g[r-2][i]==0 || g[r-2][i-1]==0)//r-2行的2列不合法
                return false;
           if( base[S2][i]!=0 || base[S2][i-1]!=0 )
                //联合状态第i位或第i-1位不是1就是2
                return false;
       }
    }
   return true;
}
 
bool legal_1(int S,int S2)//判断摆放方式S是否和g[0]和g[1]且和前两行的联合状态S2都兼容,S在这里是摆放状态不是联合状态
{
   //关于S2是否分别满足如r-1行和r-2行给的初始矩阵,我们不管,我们假设他已经满足了
   //但是要注意当按照S摆放之后 生成的S1联合状态要符合r和r-1行的初始矩阵
   int r=1;
   for(int i=0; i<m; i++) //第i列
    {
       if(base[S][i]==1)//此格左放,要考虑它的前两列格子和上一行格子,还有它本身
       {
           if(g[r][i]==0 || g[r][i-1]==0 || g[r][i-2]==0)//r行的3列不合法
                return false;
           if(r<=0 || g[r-1][i]==0 || g[r-1][i-1]==0 || g[r-1][i-2]==0)//r-1行的3列不合法
                return false;
           if( base[S2][i]==2 || base[S2][i-1]==2 || base[S2][i-2]==2  )//联合状态第i,i-1,i-2位至少有一位是2
                return false;
       }
       if(base[S][i]==2)//此格上放,要考虑它的前一列和上两行,包括它本身
       {
           return false;
       }
    }
   return true;
}
 
void play(int r,int S1)//当前处理第r行,且前两行(r-1和r-2)的联合状态是S1的三进制形式
{
   for(int i=0; i<cnt; i++)if(legal(r,state[i],S1)) //要求state[i]与g[r]兼容,且与S1兼容
       {
           int S=0;
 
           int num = 0;//计算摆放了多少个小矩阵
           for(int j=0; j<m; j++) //根据state[i]的三进制摆放状态生成当前行和上一行的联合状态
           {
                if(base[state[i]][j]==1)//第j列左放,新联合状态第j,j-1,j-2位都置2
                {
                    S +=2*mi[j]+2*mi[j-1]+2*mi[j-2];
                    num++;
                }
               elseif(base[state[i]][j]==2)//第j列上放,新联合状态第j,j-1位都置2
                {
                    S += 2*mi[j]+2*mi[j-1];
                    num++;
                }
           }
 
           for(int j=0; j<m; j++) //根据联合状态S1的第1行更新当前行联合状态S的初始状态
           {
                if(base[S1][j]==2 &&base[S][j]==0)//第r-1行j列被填充,但是第r行的j列为空,那么联合状态S第j位置1
                    S += mi[j];
           }
           d[cur][S]=max(d[cur][S],d[1-cur][S1]+num);
           if(r==n-1) sum = max(sum , d[cur][S]);//取得最大值
       }
}
 
void play_1()//当前处理第1行,且前两行0行和假象行的联合状态是S1的三进制形式=1111111 (3)
{
   //int r = 1;
   int S1 = 0;
   for(int i=0; i<m; i++) //S1为全1
       S1 += mi[i];
   for(int i=0; i<cnt; i++)if(legal_1(state[i],S1)) //要求state[i]与第1行和第0行的g兼容,且与S1兼容
    {
           int S=0;
 
           int num = 0;//计算摆放了多少个小矩阵
           for(int j=0; j<m; j++) //根据state[i]的三进制摆放状态生成当前行和上一行的联合状态
           {
                if(base[state[i]][j]==1)//第j列左放,新联合状态第j,j-1,j-2位都置2
                {
                    S +=2*mi[j]+2*mi[j-1]+2*mi[j-2];
                    num++;
                }
                else if(base[state[i]][j]==2)//第j列上放,新联合状态第j,j-1位都置2
                {
                    S += 2*mi[j]+2*mi[j-1];
                    num++;
                }
           }
 
           for(int j=0; j<m; j++) //根据联合状态S1的第1行更新当前行联合状态S的初始状态
           {
                if(base[S1][j]==2 &&base[S][j]==0)//第r-1行j列被填充,但是第r行的j列为空,那么联合状态S第j位置1
                    S += mi[j];
           }
           d[1][S]=max(d[1][S],num);
           if(n==2) sum = max(sum , d[1][S]);//取得最大值
    }
}
int main()
{
   int temp=1;//预处理幂运算相关
   for(int i=0; i<=10; i++)
    {
       mi[i]=temp;
       temp*=3;
    }
   for(int value=0; value<mi[10]; value++)
    {
       temp = value;
       int p =0;
       while(temp>0)
       {
           base[value][p++]=temp%3;
           temp/=3;
       }
    }
   int T;
   while(scanf("%d",&T)==1&&T)
    {
       while(T--)
       {
           sum = 0;//最大值初始化
           scanf("%d%d%d",&n,&m,&bad);
           if(n==1)//只有一行时特殊处理
           {
                printf("0\n");
                continue;
           }
           for(int i=0; i<n; i++) //此处可以改成 memset(g,1,sizeof(g)); 吗?
                for(int j=0; j<m; j++)
                    g[i][j]=1;
           for(int i=0; i<bad; i++)
           {
                int a,b;
               scanf("%d%d",&a,&b);
                a--;
                b--;
                g[a][b]=0;//坏格子标记为0
           }
           cnt = 0;
           for(int S=0; S<mi[m]; S++) //预生成所有合理的三进制摆放状态,在m个格子都是好格子的时候能放下就行
           {
                if(good(S))
                {
                    state[cnt++]=S;
                }
           }
           //printf("m=%d cnt=%d\n",m,cnt);
           memset(d,-1,sizeof(d));
           play_1();//生成d[1][]的所有初始合法状态值
           cur = 1;
           for(int r=2; r<n; r++)
           {
                cur = 1- cur;
                for(int S1=0; S1<mi[m];S1++) //S1的二进制形式是第r-1行的填充状态
                {
                   if(d[1-cur][S1]==-1)//(r-1,S1)是一个非法的不可生成 的状态
                        continue;
                    play(r,S1);
                }
           }
           printf("%d\n",sum);
       }
    }
   return 0;
}


 

现在使用三进制表示前两行的联合状态且使用滚动数组,还要使用dfs搜索可行的摆放状态(不能用枚举,用枚举超时).

AC代码:dfs放右上矩阵1829ms dfs放左上矩阵2000ms

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,bad,sum,cur;
int d[2][60000];
int g[155][11];
int mi[12];//幂运算
int base[60000][12];//base[x][i]=2,x的三进制形式的第i位是2
int state[300];//该state[i]=s保存的是一个三进制数,s的第i位为0表示不放,第i位为1表示左方,第i位为2表示上方
char s_3[15];//用于打印数的三进制形式测试中间结果
char *print_3(int x)//保存x的三进制形式到s_3中,且x的第0位对应第0列,对应s_3中的第0位中.
{
    for(int i=0;i<15;i++)
        s_3[i]='0';
    int p=0;
    while(x)
    {
        s_3[p++]='0'+x%3;//x的第0位对应第0列,保存在s_3的第0位中
        x/=3;
    }
    s_3[m]='\0';
    return s_3;
}

int P,Q;
void dfs(int r,int c,int num)//决策到r行c列 目前的d[][]=num
{
    if(c==m)return ;
    d[cur][Q]=max(d[cur][Q],num);
    //printf("0: d[%d][%s]=%d***P=%s***Q=%s\n",r,print_3(Q),d[cur][Q],print_3(P),print_3(Q));


    //上放
    if(c<=m-2&&base[P][c]==0&&base[P][c+1]==0&&base[Q][c]==0&&base[Q][c+1]==0)
    {

        Q += 2*mi[c];
        Q += 2*mi[c+1];
        d[cur][Q]=max(d[cur][Q],num+1);
        //printf("1: d[%d][%s]=%d***P=%s***Q=%s\n",r,print_3(Q),d[cur][Q],print_3(P),print_3(Q));

        dfs(r,c+2,num+1);
        Q -= 2*mi[c];
        Q -= 2*mi[c+1];
    }

    //右放
    if(c<=m-3&&base[Q][c]==0&&base[Q][c+1]==0&&base[Q][c+2]==0)
    {
        Q += 2*mi[c];
        Q += 2*mi[c+1];
        Q += 2*mi[c+2];
        d[cur][Q]=max(d[cur][Q],num+1);
        //printf("2: d[%d][%s]=%d***P=%s***Q=%s\n",r,print_3(Q),d[cur][Q],print_3(P),print_3(Q));

        dfs(r,c+3,num+1);
        Q -= 2*mi[c];
        Q -= 2*mi[c+1];
        Q -= 2*mi[c+2];
    }
    dfs( r, c+1, num );
}


int main()
{
    //freopen("mytest.out", "w", stdout);

    int temp=1;//预处理幂运算相关
    for(int i=0; i<=10; i++)
    {
        mi[i]=temp;
        temp*=3;
    }
    for(int value=0; value<mi[10]; value++)
    {
        temp = value;
        int p =0;
        while(temp>0)
        {
            base[value][p++]=temp%3;
            temp/=3;
        }
    }
    int T;
    while(scanf("%d",&T)==1&&T)
    {
        while(T--)
        {
            sum = 0;//最大值初始化
            scanf("%d%d%d",&n,&m,&bad);
            if(n==1)//只有一行时特殊处理
            {
                printf("0\n");
                continue;
            }
            for(int i=0; i<n; i++) //此处可以改成 memset(g,1,sizeof(g)); 吗?
                for(int j=0; j<m; j++)
                    g[i][j]=1;
            for(int i=0; i<bad; i++)
            {
                int a,b;
                scanf("%d%d",&a,&b);
                a--;
                b--;
                g[a][b]=0;//坏格子标记为0
            }

            //printf("m=%d cnt=%d\n",m,cnt);
            memset(d,-1,sizeof(d));
            int S=0;
            for(int i=0;i<m;i++)
            {
                if(g[0][i]==0) S+= 2*mi[i];
                else S+= mi[i];
            }
            d[0][S]=0;//生成d[0][]的所有初始合法状态值
            cur = 0;
            for(int r=1; r<n; r++)
            {
                cur = 1- cur;
                memset( d[cur], -1, sizeof(d[cur]) );

                for(int S1=0; S1<mi[m]; S1++) //S1的二进制形式是第r-1行的填充状态
                {
                    if(d[1-cur][S1]==-1)//(r-1,S1)是一个非法的 不可生成 的状态
                        continue;
                    //printf("d[%d][%s]=%d\n",r-1,print_3(S1),d[1-cur][S1]);
                    P = S1;
                    Q=0;
                    for(int i=0;i<m;i++)
                    {
                        if(g[r][i]==0)Q+= 2*mi[i];
                        else
                        {
                            int x;
                            x = base[P][i]-1;
                            if(x<0) x= 0;
                            Q+=x*mi[i];
                        }
                    }
                    dfs(r,0,d[1-cur][P]);//r行0列开始模拟摆放
                    //play(r,S1);
                }
            }
            for(int S=0;S<mi[m];S++)
                sum = max(sum,max(d[cur][S],d[1-cur][S]));
            printf("%d\n",sum);
        }
    }
    return 0;
}



 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值