• 【CJOJ1793】【USACO 4.3.2】素数方阵


    题面

    Description

    在下面的方格中,每行,每列,以及两条对角线上的数字可以看作是五位的素数。方格中的行按照从左到右的顺序组成一个素数,而列按照从上到下的顺序。两条对角线也是按照从左到右的顺序来组成。

    这里写图片描述

    这些素数各个数位上的和必须相等。
    左上角的数字是预先定好的。
    一个素数可能在方阵中重复多次。
    如果不只有一个解,将它们全部输出(按照这25个数字组成的25位数的大小排序)。
    一个五位的素数开头不能为0(例如:00003 不是五位素数)

    Input

    一行包括两个被空格分开的整数:各个位的数字和 和左上角的数字。

    Output

    对于每一个找到的方案输出5行,每行5个字符, 每行可以转化为一个5位的质数.在两组方案中间输出一个空行. 如果没有解就单独输出一行"NONE"。

    Sample Input

    11 1

    Sample Output

    11351
    14033
    30323
    53201
    13313

    11351
    33203
    30323
    14033
    33311

    13313
    13043
    32303
    50231
    13331

    题解

    这道题,搜索好题。

    先说我的第一种方法:

    首先筛选质数(一个很显然的优化:只需要数字和为给定值的质数)
    然后是将质数建立字典树
    然后从第一列开始,每列填写一个质数,然后每行每对角线依次检查,判断是否可行。每次的解先存起来,最后排序、输出。

    长长的代码

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<vector>
    #include<algorithm>
    using namespace std;
    bool zs(int x)
    {
    	  int t=sqrt(x);
    	  for(int i=2;i<=t;++i)
    	    if(x%i==0)return false;
    	  return true;
    }
    int cnt=0,tot=0,pri[5000];
    int S,A;
    int g[6][6];
    int number[5000][6];
    struct Node
    {
    	  int vis[10];//字典树 
    }t[100000];
    struct Ans
    {
    	  int g[6][6];
    }ans[100];
    bool operator <(Ans a,Ans b)
    {
    	   for(int i=1;i<=5;++i)
    	    for(int j=1;j<=5;++j)
    	      if(a.g[i][j]!=b.g[i][j])
    	        return a.g[i][j]<b.g[i][j];
    }
    int sum=0;
    inline void outp()
    {
    	   for(int i=1;i<=5;++i)
    	   {
    	   	     for(int j=1;j<=5;++j)
    	   	       printf("%d",g[i][j]);
    	   	     printf("
    ");
    	   }
    }
    inline void Record()//记录答案 
    {
    	   sum++;
    	   for(int i=1;i<=5;++i)
    	    for(int j=1;j<=5;++j)
    	      ans[sum].g[i][j]=g[i][j];
    }
    inline void Add(int x)//判断是否可行并构建字典树 
    {
    	 int a[6]={0,x/10000,x/1000%10,x/100%10,x/10%10,x%10};
    	 if(a[1]+a[2]+a[3]+a[4]+a[5]!=S)return;
    	 if(!zs(x))return;
    	 int now=0;
    	 ++tot;//满足条件的素数 
    	 pri[tot]=x;
    	 //number[tot]=a;
    	 //cout<<pri[tot]<<endl;
    	 for(int i=1;i<=5;++i)//构造字典树 
    	 {
    	 	    if(t[now].vis[a[i]]==0)
    	 	        t[now].vis[a[i]]=++cnt;
    	 	    now=t[now].vis[a[i]];
    	 }
    }
    inline bool check(int x)//检查到第x列 
    {
    	   int now,s; 
    	   //检验左上到右下对角线
    	   s=now=0;
    	   for(int j=1;j<=x;++j)
    	   {
    	   	     s+=g[j][j];
    	   	     if(t[now].vis[g[j][j]]==0)return false;
    	   	     now=t[now].vis[g[j][j]];
           }
           if(s>S)return false;//如果和已经大于S 
           if(x==5&&s!=S)return false;//最后的和必须为S
    	   //检验左下到右上对角线
    	   s=now=0;
    	   for(int j=1;j<=x;++j)
    	   {
    	   	     s+=g[6-j][j];
    	   	     if(t[now].vis[g[6-j][j]]==0)return false;
    	   	     now=t[now].vis[g[6-j][j]];
           }
           if(s>S)return false;//如果和已经大于S 
           if(x==5&&s!=S)return false;//最后的和必须为S
    	   for(int i=1;i<=5;++i)//枚举行
           {
           	      now=s=0;
           	      for(int j=1;j<=x;++j)
           	      {
           	      	     s+=g[i][j];
    					 if(t[now].vis[g[i][j]]==0)return false;
    					 now=t[now].vis[g[i][j]];//字典树	   
           	      }
           	      if(s>S)return false;//如果和已经大于S 
           	      if(x==5&&s!=S)return false;//最后的和必须为S 
           }
           
           return true;
    }
    void DFS(int x)
    {
    	   //outp();
    	   //cout<<endl;
    	   if(!check(x-1))return;//不合要求 
    	   if(x==6)
    	   {
    	   	      Record();
    	   	      //outp();
    	   	      return;
    	   }
    	   for(int i=1;i<=tot;++i)//填数 
    	   {
    	   	        g[1][x]=pri[i]/10000;
    	   	        g[2][x]=pri[i]/1000%10;
    	   	        g[3][x]=pri[i]/100%10;
    	   	        g[4][x]=pri[i]/10%10;
    	   	        g[5][x]=pri[i]%10;
    				DFS(x+1); 
    			    g[1][x]=g[2][x]=g[3][x]=g[4][x]=g[5][x]=-1;//回朔
    	   }
    }
    int main()
    {
    	  freopen("prime.in","r",stdin);
    	  freopen("prime.out","w",stdout);
          cin>>S;
          cin>>A;
          for(int i=10003;i<=99999;i+=2)
             Add(i);
          memset(g,-1,sizeof(g));
          for(int i=1;i<=tot;++i)//枚举第一列
    	  {
    	  	    if(pri[i]/10000==A)
    	  	    {
    	  	    	    g[1][1]=A;
    	   	            g[2][1]=pri[i]/1000%10;
    	   	            g[3][1]=pri[i]/100%10;
    	   	            g[4][1]=pri[i]/10%10;
    	   	            g[5][1]=pri[i]%10;
    	  	    	    DFS(2);//搜索 
    	  	    }
    	  	    if(pri[i]/10000>A)break;
          }
          sort(&ans[1],&ans[sum+1]);
          for(int k=1;k<=sum;++k)
          {
    	       for(int i=1;i<=5;++i)
    	       {
    	          for(int j=1;j<=5;++j)
    	            printf("%d",ans[k].g[i][j]);
    	          printf("
    ");
    	       }
    	       printf("
    ");
    	  }
    	  if(sum==0)
    	    cout<<"NONE"<<endl;
    	  return 0;
    }
    
    

    这样子求解还是很容易的,但是,并不能够随时判断一个质数是否能够填上去,导致会求出大量的无用解法,导致超时。 ![这里写图片描述](http://img.blog.csdn.net/20170706114343691?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzA5NzQzNjk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
    那么,证明这样一列一列填还是效率太低。

    然后看我的第二种解法:
    首先还是找出所有满足条件的质数,
    暴力求出他们的前缀和后缀(其实建立字典树也是可以的)
    接下来,每次搜索一个格子(从第一行开始,自左向右,自上而下),
    枚举填的数,并且判断是否可行(判断每一行是否存在前缀,每一列是否存在前缀,主对角线是否存在前缀,副对角线是否存在后缀),这样就能够减去大量的不合理情况。

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<vector>
    #include<algorithm>
    using namespace std;
    inline bool zs(int x)
    {
    	  int t=sqrt(x);
    	  for(int i=2;i<=t;++i)
    	    if(x%i==0)return false;
    	  return true;
    }
    int S,A;
    int g[6][6];
    int hz[1000001],qz[1000001];
    int pp[6]={1,10,100,1000,10000,100000};
    int sum=0;
    int L[10],R[10];
    int djx1,djx2;
    inline void outp()
    {
    	   for(int i=1;i<=5;++i)
    	   {
    	   	     printf("%d
    ",L[i]);
    	   }
    	   printf("
    ");
    }
    inline void Add(int x)//判断是否可行
    {
    	 int a[6]={0,x/10000,x/1000%10,x/100%10,x/10%10,x%10};
    	 if(a[1]+a[2]+a[3]+a[4]+a[5]!=S)return;
    	 if(!zs(x))return;
    	 int i=x;
    	 qz[i/10000]=qz[i/1000]=qz[i/100]=qz[i/10]=qz[i]=true;//前缀
    	 hz[i%10000]=hz[i%1000]=hz[i%100]=hz[i%10]=hz[i]=true;//后缀
    }
    void DFS(int x,int y)
    {
    	   if(y==6)//换行 
    	   {
    	   	     y=1;x+=1;
    	   }
    	   if(x==6)//输出解 
    	   {
                  ++sum;
                  outp();
                  return;
    	   }
    	   bool ok1,ok2,ok3,ok4;	
    	   for(int i=0;i<=9;++i)//枚举当前数字
    	   {
    			 ok1=qz[L[x]*10+i];//行的值要在前缀中
    			 ok2=qz[R[y]*10+i];//列的值要在前缀中
    			 ok3=((x!=y)||(x==y&&qz[djx1*10+i]));//对角线的值要在前缀中
    			 ok4=((x+y!=6)||(x+y==6&&hz[djx2+i*pp[x-1]]));//对角线的值要在后缀中
    			 
    			 if(!(ok1&&ok2&&ok3&&ok4))continue;
    			 
    			 L[x]=L[x]*10+i;
    			 R[y]=R[y]*10+i;
    			 if(x==y)  djx1=djx1*10+i;
    			 if(x+y==6)djx2=djx2+i*pp[x-1];
    			 
    			 DFS(x,y+1);   //向下搜索
    			 
    			 L[x]/=10;
    			 R[y]/=10;
    			 if(x==y)  djx1/=10;
    			 if(x+y==6)djx2-=i*pp[x-1];
           }
    }
    int main()
    {
    	  freopen("prime.in","r",stdin);
    	  freopen("prime.out","w",stdout);
          cin>>S;
          cin>>A;
          for(int i=10003;i<=99999;i+=2)
               Add(i);
          memset(g,-1,sizeof(g));
          g[1][1]=A;
          L[1]=R[1]=djx1=A;	
          DFS(1,2);
    	  if(sum==0)
    	    cout<<"NONE"<<endl;
    	  return 0;
    }
    
    

    这种方法能够AC,但是效率很低。
    这里写图片描述

    AC归为AC

    其实这题如果更改搜索顺序和得到更好地优化。
    并且很明显的一点就是,如果求出了某 行/列/对角线 上的四个数,最后一个是可以直接计算判断的(效率更高)。【但是我很懒,没有继续优化了】

  • 相关阅读:
    ※剑指offer系列51:二叉搜索树的第k个结点
    ※剑指offer系列50:序列化二叉树
    sqlserver添加表注释、字段注释
    3-实体数据模型与LINQ-where&OfType
    3-实体数据模型与LINQ-Select
    Jquery 在子页面上设置父页面元素的值
    开发注意事项
    函数的进阶
    文件操作的相关内容
    基本数据类型----dict
  • 原文地址:https://www.cnblogs.com/cjyyb/p/7197184.html
Copyright © 2020-2023  润新知