如果发现广告等破坏行为,请尽量将条目恢复到较早的版本而不是把相应内容直接删除,谢谢合作。

USACO/prime3

来自"NOCOW"

跳转到: 导航, 搜索
 这篇文章可以证实是由NOCOW用户原创,不存在任何版权争议。
 本文作者同意以GNU FDLCC-by-saGNU LGPL(如果适用)三种版权发布此文章(不包括翻译文章中属于原始所有者的部分版权)。
 如果你修改了这篇文章并且不同意用GNU FDL以外的版权发布,可以换一个版权模板或者移除此模板。
这是USACO Chapter 4 .3中的OI题目The Primes介绍及题解,参见 翻译C语言代码C++语言代码Pascal语言代码


目录

[编辑] 问题分析

这道题比较难。 采取枚举模拟搜索,但是需要优化。

[编辑] 优化技巧

一行行的枚举连样例都过不了,考虑这题格子之间有相互约束,所以首先要优化枚举顺序。先枚举两条对角线,因为他们控制的行列是最多的,而且左上角的数已经确定了。接着枚举中间的一竖列,因为它同样控制的行列最多,接着枚举最上面一行和最下面一行。此时第3行的2,4两个数也可以通过减法得到了。最后枚举第2,4行,第3行减法得到。于是所有格子都已经确定了,验证一下就OK。

数据结构方面,需要多个数组记录满足条件的素数,比如已知第1,3,5位的所有素数或已知第1位的所有素数都要预处理出来。并且每个数用5个1位数储存,这样操作方便而且效率高。

全程都需要时刻剪去不满足要求的情况。

注意细节,此题相当繁杂。

                                                                          By Leser Yang.

其实用for暴力枚举不烦,但是写错就完蛋了,调试要累死的.

[编辑] 谁说顺序枚举过不了

  Test 1: TEST OK [0.011 secs, 3092 KB]
  Test 2: TEST OK [0.011 secs, 3092 KB]
  Test 3: TEST OK [0.043 secs, 3092 KB]
  Test 4: TEST OK [0.065 secs, 3092 KB]
  Test 5: TEST OK [0.076 secs, 3092 KB]
  Test 6: TEST OK [0.270 secs, 3092 KB]
  Test 7: TEST OK [0.346 secs, 3092 KB]
  Test 8: TEST OK [0.464 secs, 3092 KB]
  Test 9: TEST OK [0.821 secs, 3092 KB]
  Test 10: TEST OK [1.156 secs, 3092 KB]

用了15重循环枚举+类Trie+位运算(类似Checker),写起来很爽,一次AC。。。 但用时确实不会很快。。。 具体见C++源代码


你程序牛逼,囧rz --路人
你的程序相当牛逼,OTZ。在下佩服得五体投地 -- By 路人乙 你的程序确实相当相当牛逼,囧rz Orz。在下看了仰天长叹 -- By 路人丙 你的程序实在是太太太太牛牛牛牛逼,囧rz Orz,各种rz。在下看了各种佩服外加无限仰慕 -- By 路人丁

[编辑] 递归的顺序枚举都能过

  Test 1: TEST OK [0.011 secs, 2280 KB]
  Test 2: TEST OK [0.022 secs, 2280 KB]
  Test 3: TEST OK [0.043 secs, 2280 KB]
  Test 4: TEST OK [0.097 secs, 2280 KB]
  Test 5: TEST OK [0.108 secs, 2280 KB]
  Test 6: TEST OK [0.346 secs, 2280 KB]
  Test 7: TEST OK [0.464 secs, 2280 KB]
  Test 8: TEST OK [0.594 secs, 2280 KB]
  Test 9: TEST OK [0.994 secs, 2280 KB]
  Test 10: TEST OK [1.339 secs, 2280 KB]

只用了lowbit和原始的递归

搜了4*4的方阵,对于任何行、列、主副对角线 都用lowbit 剪枝即可。

具体见C源码……

[编辑] 我的递归顺序枚举

TASK: prime3 LANG: C++

Compiling... Compile: OK

Executing...

  Test 1: TEST OK [0.022 secs, 3412 KB]
  Test 2: TEST OK [0.032 secs, 3412 KB]
  Test 3: TEST OK [0.043 secs, 3412 KB]
  Test 4: TEST OK [0.054 secs, 3412 KB]
  Test 5: TEST OK [0.076 secs, 3412 KB]
  Test 6: TEST OK [0.097 secs, 3412 KB]
  Test 7: TEST OK [0.130 secs, 3412 KB]
  Test 8: TEST OK [0.151 secs, 3412 KB]
  Test 9: TEST OK [0.194 secs, 3412 KB]
  Test 10: TEST OK [0.227 secs, 3412 KB]

All tests OK. 用的是顺序枚举,速度很快 具体顺序:

   0 20 22 23  8   
  13  1 17  7 15
  9  10  2 11 12
  14  6 18  3 16 
   5 19 21 24  4

加粗的可以推出来,13重for 详见bc_zhan1代码

[编辑] 另一种思路

Executing...

  Test 1: TEST OK [0.011 secs, 4852 KB]
  Test 2: TEST OK [0.011 secs, 4852 KB]
  Test 3: TEST OK [0.022 secs, 4852 KB]
  Test 4: TEST OK [0.162 secs, 4852 KB]
  Test 5: TEST OK [0.065 secs, 4852 KB]
  Test 6: TEST OK [0.216 secs, 4852 KB]
  Test 7: TEST OK [0.259 secs, 4852 KB]
  Test 8: TEST OK [0.486 secs, 4852 KB]
  Test 9: TEST OK [0.594 secs, 4852 KB]
  Test 10: TEST OK [0.972 secs, 4852 KB]

方法: 保存两个符合要求的10000..100000质数表,一个质数表中的质数须满足最高位为矩阵的第1个数且全部为非零位; 另一个质数表的质数满足各数位均非偶数; 两个表中的质数的各位之和为输入值。想必读者已经猜到了,矩阵最外一层的四个质数必然分别在这两个表中。而经过实际测试表明,两个表的大小基本上不会超过100,由此只需四重循环枚举,再判断内部的9个数字。 经过计算可得出矩阵中央位置的数字可有最外层的16个数字直接确定。因此只需再枚举2个数,即可确定剩余的数字。

                                                                               By Ever_ljq.

[编辑] 优化顺序枚举

前7个点0.000secs 后3个点0.027secs

USER: Wang Qian [wangqia6] TASK: prime3 LANG: C++

Compiling... Compile: OK

Executing...

  Test 1: TEST OK [0.000 secs, 8068 KB]
  Test 2: TEST OK [0.000 secs, 8068 KB]
  Test 3: TEST OK [0.000 secs, 8068 KB]
  Test 4: TEST OK [0.000 secs, 8068 KB]
  Test 5: TEST OK [0.000 secs, 8068 KB]
  Test 6: TEST OK [0.000 secs, 8068 KB]
  Test 7: TEST OK [0.000 secs, 8068 KB]
  Test 8: TEST OK [0.027 secs, 8068 KB]
  Test 9: TEST OK [0.027 secs, 8068 KB]
  Test 10: TEST OK [0.027 secs, 8068 KB]

All tests OK.

[编辑] DFS手动指定搜索顺序

其实和for循环的做法差不多, 就是在DFS里写上25个case..但是速度要快得多, 最后一个测试点0.1s 搜索顺序是这样的:

0 21 18 22  5

12 2 13 8 9

16 23 3 24 10

14 7 15 4 11

6 19 17 20  1

大致分为几种情况{sum表示当前行\列\对角线未填数字之和}:

1)非末位. 边界条件是 0 <= i <= min{sum, 9}

2)末位. 边界条件是i = 1, 3, 7, 9(i <= sum)

3)可计算位(即该行或列或对角线已经填了4个数). sum < 10, 判断是否为素数, dfs下一层时, sum-下一层已填充位置.

4)对于由情况3)转移而来的状态, 需要检查sum >= 0.

详见C++代码.

--Climber.pI

[编辑] 范例程序

个人工具