【USACO4.3.2】素数方阵 搜索搜索!

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;

注意到字典树的make()函数么!

没错!这是一个核心优化!让程序从运行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;
}

倒着插入一个数字为k的五位数。比如12345,插入进去为54321

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< sou[10]; i != -1; i = k -> sou[i])
	{
		if (k -> next[i])
		{
			output[t] = i;
			pg(k -> next[i], t + 1);
		}	
	}
}
*/
void init()
{
	cin >> tot >> p;
	for (int i = 10000; i <= 99999; ++ i)
	{
		if (weihe(i) != tot)	continue;
		for (int j = 2; j <= sqrt(i); ++ j)
			if (i % j == 0)	goto i1;
		prim[i] = true;
		a[++t] = i;
		insert(i);
		insert2(i);
		i1:;
	}

	change(root, 1);
	change(root2, 1);
}

int x[10][10];
node *link[10];
bool FLAG = false;
inline void check()
{
	for (int i = 1; i <= 5; ++ i) //列
	{
		int sb = 0;
		for (int j = 1; j <= 4; ++ j)	sb += x[j][i];
		x[5][i] = tot - sb;

	}
	int tmp3 = x[5][1] * 10000 + x[5][2] * 1000 + x[5][3] * 100 + x[5][4] * 10 + x[5][5];
	if (!prim[tmp3])	return;
	int tmp1 = x[1][1] * 10000 + x[2][2] * 1000 + x[3][3] * 100 + x[4][4] * 10 + x[5][5];//左对角线
	int tmp2 = x[5][1] * 10000 + x[4][2] * 1000 + x[3][3] * 100 + x[2][4] * 10 + x[1][5];//右对角线
	if (prim[tmp1] && prim[tmp2])
	{
		if (FLAG)	printf("\n");
		FLAG = true;
		for (int i = 1; i <= 5; ++ i)
		{
			for (int j = 1; j <= 4; ++ j)	printf("%d", x[i][j]);
			printf("%d\n", x[i][5]);
		}
	}

}

node *e;
inline bool xie(int k)
{
	e = root -> next[x[1][1]];
	for (int i = 2; i <=k; ++ i)
	{
		e = e -> next[x[i][i]];
		if (!e)	return false;
	}
	return true;
}

inline bool shang(int k)
{
	e = root2 -> next[x[1][5]];
	for (int i = 2; i <= k; ++ i)
	{
		e = e -> next[x[i][6 - i]];
		if (!e)	return false;
	}
	return true;
}

void dfs(int k, node *link1, node *link2, node *link3, node *link4, node *link5) //第k 行
{
	if (k == 5)//第五行不填写,直接算出来
	{
		check();
		return;	
	}

#define e1 x[k][1]
#define e2 x[k][2]
#define e3 x[k][3]
#define e4 x[k][4]
#define e5 x[k][5]
	node *c_link1, *c_link2, *c_link3, *c_link4, *c_link5;
	for (e1 = link1 -> sou[10]; e1 != -1; e1 = link1 -> sou[e1])
	{
		c_link1 = root -> next[e1];
		if (c_link1 == NULL)	continue;
		for (e2 = link2 -> sou[10]; e2 != -1; e2 = link2 -> sou[e2])
		{
			c_link2 = c_link1 -> next[e2];
			if (c_link2 == NULL)	continue;
			if (k == 2)	if (!xie(2))	continue;
			if (k == 4)	if (!shang(4))	continue;
			for (e3 = link3 -> sou[10]; e3 != -1; e3 = link3 -> sou[e3])
			{
				c_link3 = c_link2 -> next[e3];
				if (c_link3 == NULL)	continue;
				if (k == 3)	if (!xie(3))	continue;
				if (k == 3)	if (!shang(3))	continue;
				for (e4 = link4 -> sou[10]; e4 != -1; e4 = link4 -> sou[e4])
				{
					c_link4 = c_link3 -> next[e4];
					if (c_link4 == NULL)	continue;
					if (k == 4)	if (!xie(4))	continue;
					if (k == 2)	if (!shang(2))	continue;
					for (e5 = link5 -> sou[10]; e5 != -1; e5 = link5 -> sou[e5])
					{
						c_link5 = c_link4 -> next[e5];
						if (c_link5 == NULL)	continue;
						if (root2 -> next[e5] == NULL)	continue;
						dfs(k + 1, link1 -> next[e1], link2 -> next[e2], link3 -> next[e3], link4 -> next[e4], link5 -> next[e5]);
					}
				}
			}
		}
	}
}

void doit()
{
	x[1][1] = p; //第一个地方填上题目给的p这个数字
	node *row1 = root -> next[p], *col1 = root -> next[p]; 
	int t=0;
	for (int s = p * 10000; s < (p + 1) * 10000; ++ s)
	{
		if (!prim[s])	continue;	//必须先合法,不合法直接先退出
//		cout< next[s / 10000];
		link[2] = root -> next[s % 10000 / 1000];
		link[3] = root -> next[s % 1000 / 100];
		link[4] = root -> next[s % 100 / 10];
		link[5] = root -> next[s % 10];
		if (link[1] == NULL || link[2] == NULL || link[3] == NULL || link[4] == NULL || link[5] == NULL)	continue;
		x[1][1] = link[1] -> a;
		x[1][2] = link[2] -> a;
		x[1][3] = link[3] -> a;
		x[1][4] = link[4] -> a;
		x[1][5] = link[5] -> a;
		dfs(2, link[1], link[2], link[3], link[4], link[5]);
		
	}
	if (!FLAG)	printf("NONE\n");
	//cout<



你可能感兴趣的:(USACO,搜索)