题面

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



这样子求解还是很容易的,但是,并不能够随时判断一个质数是否能够填上去,导致会求出大量的无用解法,导致超时。
![这里写图片描述](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\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

其实这题如果更改搜索顺序和得到更好地优化。

并且很明显的一点就是,如果求出了某 行/列/对角线 上的四个数,最后一个是可以直接计算判断的(效率更高)。【但是我很懒,没有继续优化了】

【CJOJ1793】【USACO 4.3.2】素数方阵的更多相关文章

  1. 素数方阵的工程ing

    2016 12 12 16 12 开始 2016 12 13 17 30   还没开打 2017 1 3 ..... 一星期前貌似打完了... 如下 #include<iostream> ...

  2. CSU训练分类

    √√第一部分 基础算法(#10023 除外) 第 1 章 贪心算法 √√#10000 「一本通 1.1 例 1」活动安排 √√#10001 「一本通 1.1 例 2」种树 √√#10002 「一本通 ...

  3. 177. [USACO Jan07] 有限制的素数

    177. [USACO Jan07] ★   输入文件:qprime.in   输出文件:qprime.out   简单对比 时间限制:1 s   内存限制:128 MB Farmer John 开始 ...

  4. USACO1.5Superprime Rid[附带关于素数算法时间测试]

    题目描述 农民约翰的母牛总是产生最好的肋骨.你能通过农民约翰和美国农业部标记在每根肋骨上的数字认出它们.农民约翰确定他卖给买方的是真正的质数肋骨,是因为从右边开始切下肋骨,每次还剩下的肋骨上的数字都组 ...

  5. [USACO精选] 第一章 数值计算

    好不容易坑来了传说中的USACO精选,近100题我要是能做完就哈哈哈哈了…继今天学并查集连番受挫之后,决定写一写基础题. #0 负二进制 2014-01-10 其实是想到就会做,不想到就不会做的题,数 ...

  6. (Step1-500题)UVaOJ+算法竞赛入门经典+挑战编程+USACO

    http://www.cnblogs.com/sxiszero/p/3618737.html 下面给出的题目共计560道,去掉重复的也有近500题,作为ACMer Training Step1,用1年 ...

  7. USACO Chapter 1 解题总结

    USACO Chapter 1 解题总结 1.1.1 Your Ride Is Here 基本字符串操作,无压力. 1.1.2 Greedy Gift Givers 基础模拟题,弄明白题意,不怕麻烦, ...

  8. USACO chapter1

    几天时间就把USACO chapter1重新做了一遍,发现了自己以前许多的不足.蒽,现在的程序明显比以前干净很多,而且效率也提高了许多.继续努力吧,好好的提高自己.这一章主要还是基本功的训练,没多少的 ...

  9. 丑数(USACO)

    这个题是一个动态规划加优化的经典题 1246 丑数 USACO  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 钻石 Diamond 题解 题目描述 Description 对 ...

随机推荐

  1. C语言头文件中定义全局变量导致重复定义错误

    合作方升级SDK后,程序编译出现变量重复定义的错误,通过错误提示无法找到什么位置重复定义了,但确定是引入新SDK后才出现的错误,从SDK的头文件中查找,最终发现在头文件中定义了全局变量 我们的项目在多 ...

  2. 让我们一起摇摆 Turnipbit体感遥控车

    相信大家对Microbit是非常熟悉的,而今天我们做的这个小玩具所用的核心板是Turnipbit,是基于 Microbit开发的一块适合儿童适用的板卡.基本使用原理是一样的,这里就不多介绍了,代码这方 ...

  3. Centos 6.7 KVM下安装windows 7系统

    装windows 虚拟机之前你需要做以下准备工作:A.下载windows 镜像文件和 windows 的驱动iso 文件到本地,然后上传至服务器的某个目录(当然你也可以直接在服务器上下载):B.你本地 ...

  4. 搭建dnsmasq服务器,局域网内部解析

    系统:centos6.5 公司内部需求一台dns server,解析内部域名(该域名不需要在公网上解析) 安装了"bind bind-utils"包,配置里设置转发到外部电信dns ...

  5. php获取今日开始时间戳和结束时间戳

    1.php获取今日开始时间戳和结束时间戳  $beginToday=mktime(0,0,0,date('m'),date('d'),date('Y'));$endToday=mktime(0,0,0 ...

  6. eslint 入门项目搭建过程

    github 地址 : https://github.com/gebin/eslint-demo 运行该项目 npm install npm start 访问 http://localhost:900 ...

  7. 五分钟了解Hash算法

    Hash算法详解 想象一下如果高级语言(Java,C++ ,C#)中如果没有实现类似List.Map等数据结构,企业级应用开发将是多么痛苦的事吧? Key-Value这种数据结构对于数据处理非常方便. ...

  8. Linux 每日命令行

    uptime 用于查看系统的负载信息. 它依次显示 当前系统时间.系统已运行时间.启用终端数量及平均负载值等信息.平均负载指的是系统在最近1分钟.5分钟.15分钟内的压力情况:负载值越低越好,尽量不要 ...

  9. EntityFramework Core 2.0自定义标量函数两种方式

    前言 上一节我们讲完原始查询如何防止SQL注入问题同时并提供了几种方式.本节我们继续来讲讲EF Core 2.0中的新特性自定义标量函数. 自定义标量函数两种方式 在EF Core 2.0中我们可以将 ...

  10. Servlet中文乱码问题解决办法

    首先对于源jsp网站和servlet里面的字符集要一样,一般支持中文的字符集为UTF-8最好采用这个字符集(除此之外还有gb2312); 对于源jsp文件的代码中需要设置 设置你的page里面的字符集 ...