【CJOJ1793】【USACO 4.3.2】素数方阵

59 篇文章 0 订阅
18 篇文章 0 订阅

题面

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("\n");
       }
}
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("\n");
           }
           printf("\n");
      }
      if(sum==0)
        cout<<"NONE"<<endl;
      return 0;
}


这样子求解还是很容易的,但是,并不能够随时判断一个质数是否能够填上去,导致会求出大量的无用解法,导致超时。
这里写图片描述

那么,证明这样一列一列填还是效率太低。

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

#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\n",L[i]);
       }
       printf("\n");
}
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

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值