Executing... Test 1: TEST OK [0.035 secs, 4000 KB] Test 2: TEST OK [0.032 secs, 4000 KB] Test 3: TEST OK [0.084 secs, 4264 KB] Test 4: TEST OK [0.105 secs, 4264 KB] Test 5: TEST OK [0.132 secs, 4264 KB] Test 6: TEST OK [0.362 secs, 4264 KB] Test 7: TEST OK [0.494 secs, 4264 KB] Test 8: TEST OK [0.672 secs, 4396 KB] Test 9: TEST OK [1.080 secs, 4396 KB] Test 10: TEST OK [1.380 secs, 4396 KB] All tests OK.
认为我程序很慢的就不用看了- - 因为nocow上各种更快的方案。 其实就是搜索顺序不一样,我是按顺序搜索的……各种优化才通关。并且在做的时候,发现,先搜索对角线的确会更快。 为啥? 后面我会提到。
首先想到的方案:穷举所有情况,判定是否有解。
根据题目,他有各种限制条件!行列和斜线。
题目要求,每一行每一列和对角线的,这个【五位素数】的各个数字的和,是有限制的,假设为tot.
首先,我们可以先得到10000~99999之间所有【5位素数】各个数字和等于tot素数找出来。 用字典树存起来。 为何要字典树呢? 因为方便啊!
求一个五位数,各位数字和(个位,十位,百位……的和)
inline int weihe(int k) //求k这个五位数,各个数字的和
{
int ans = 0;
while (k)
{
ans += k % 10;
k/=10;
}
return ans;
}
字典树:
struct node //字典树
{
int a;
bool flag;
node *next[10];
int sou[15];
node(int k)
{
a = k;
memset(next, NULL, sizeof(next));
flag = false;
}
node(){
a = -1;
memset(next, NULL, sizeof(next));
flag = false;
}
void make() //字典树构造类dancing link
{
int i, t;
memset(sou, -1, sizeof(sou));
for (i = 0; i <= 9; ++ i)
if (next[i])
{
sou[10] = i;
t = i;
break;
}
for ( ++ i;i <= 9; ++ i)
if (next[i])
{
sou[t] = i;
t = i;
}
}
}Tnull, *root = &Tnull, Tnull2, *root2 = &Tnull2;
没错!这是一个核心优化!让程序从运行7秒加速到1秒3!!这个优化很好想,但是没想到优化效果如此高。
因为字典树的next[0], next[1]...next[9]这几节点,很多节点是空的,所以用链表的想法,直接用sou[10]表示要搜的第一个next[sou[10]], 假如sou[10] = k; 那么第二个要搜的节点就是next[sou[k]]. 这个优化为何如此之屌? 其实本来就很屌…… 只不过一个,就让常数快2倍,4层循环用4次,就快7~8倍,这个优化效果是叠加的……
字典树的插入函数:
插入一个数字为k的五位数
int tmp[10];
inline void insert(int k)
{
for (int i = 5; i >= 1; k/=10, -- i) tmp[i] = k % 10;
node *e = root;
for (int i = 1; i <= 5; ++ i)
{
if (e -> next[tmp[i]] == NULL) e -> next[tmp[i]] = new node(tmp[i]);
e = e -> next[tmp[i]];
}
e -> flag = true;
}
inline void insert2(int k)
{
for (int i = 1; i <= 5; k/=10, ++ i) tmp[i] = k % 10;
node *e = root2;
for (int i = 1; i <= 5; ++ i)
{
if (e -> next[tmp[i]] == NULL) e -> next[tmp[i]] = new node(tmp[i]);
e = e -> next[tmp[i]];
}
e -> flag = true;
}
关于字典树的部分介绍完毕……其实还是要说的是核心搜索方案
直接根据我的“合法素数表”,穷举第一行可以填的5个数字。
然后每一列用一个【字典树指针】,分别往下指,因为可以确定的是,我避免了所有不必要的穷举。 同时,利用k-1行的字典树指针,在构造第k行(因为有5个字典树指针,所以直接利用字典树指针,直接构造第k行的5个新的字典树指针)的同时,再按列,判断我这构造的这一行是否合法。因为我已经有字典树了,所以只构造了第一列,第二列的时候,我就可以利用字典树指针来判断是否可行。
比如
a b c d e
f g
我在利用a,得到f, b得到g的时候,就可以判断f, g是否可以是一个“合法素数”的开头。(我的字典树中,只有af az ax开头的,所以第一列a开头的,只穷举了af,az,ax这3个情况,大大提高了效率。)
这里的程序实现非常恶心和复杂。
我用的是一个DFS(k, *p1, *p2, *p3, *p4, *p5)的形式,k表示要搜索第k行, *p1到*p5分别是直接传递的字典树指针。
当k=5的时候,并不需要穷举第五行的情况,根据已经有的数据,可以直接计算出第五行的情况。
优化1:
[a] b c d e
f [g]
其实可以判断a,g是否合法了~ a , g是否是可以合法素数的开头。
[a] b c d e
f [g] h i j k
l m [n]
同样,在穷举到这个情况的时候,也可以判断是否合法。
这是左对角线,右对角线同样也可以check. 所以…… 我上面有“倒着插入一个数字为k的五位数。”的函数。
问题来了~ 我为何说如果穷举主对角线更优呢?
因为我的程序在极限数据下,出现了一个情况。
有7000+组情况,出现了
q w e r t
y u i o p
a s d f g
h j k l z
x c v b n
也就是25的数字都填写完毕,每行每列都合法。
【q u d l ?】 【? j d o t】 合法。 但是【q u d l n】或者【x j d o t】不合法!!!!! 这情况高达7000组!而答案却只有100多个。 所以先穷举对角线是很重要的。
虽然AC了,但是因为穷举顺序的问题,方法并不优秀。
/*
TASK:prime3
LANG:C++
*/
#include
#include
#include
#include
#include
using namespace std;
int tot, p;
bool prim[100000]={false};
int a[100000], t=0;
inline int weihe(int k) //求k这个五位数,各个数字的和
{
int ans = 0;
while (k)
{
ans += k % 10;
k/=10;
}
return ans;
}
struct node //字典树
{
int a;
bool flag;
node *next[10];
int sou[15];
node(int k)
{
a = k;
memset(next, NULL, sizeof(next));
flag = false;
}
node(){
a = -1;
memset(next, NULL, sizeof(next));
flag = false;
}
void make() //字典树构造类dancing link
{
int i, t;
memset(sou, -1, sizeof(sou));
for (i = 0; i <= 9; ++ i)
if (next[i])
{
sou[10] = i;
t = i;
break;
}
for ( ++ i;i <= 9; ++ i)
if (next[i])
{
sou[t] = i;
t = i;
}
}
}Tnull, *root = &Tnull, Tnull2, *root2 = &Tnull2;
int tmp[10];
inline void insert(int k)
{
for (int i = 5; i >= 1; k/=10, -- i) tmp[i] = k % 10;
node *e = root;
for (int i = 1; i <= 5; ++ i)
{
if (e -> next[tmp[i]] == NULL) e -> next[tmp[i]] = new node(tmp[i]);
e = e -> next[tmp[i]];
}
e -> flag = true;
}
inline void insert2(int k)
{
for (int i = 1; i <= 5; k/=10, ++ i) tmp[i] = k % 10;
node *e = root2;
for (int i = 1; i <= 5; ++ i)
{
if (e -> next[tmp[i]] == NULL) e -> next[tmp[i]] = new node(tmp[i]);
e = e -> next[tmp[i]];
}
e -> flag = true;
}
inline void change(node *k, int t)
{
(*k).make();
if (t == 6) return;
for (int i = 0; i <= 9; ++ i)
if (k -> next[i])
change(k -> next[i], t + 1);
}
/*
* 调试Trie用的
int output[100] ;
void pg(node *k,int t)
{
if (t == 6)
{
for (int i = 1; i<= 5; ++ i) cout<