puzzle(0121)《一笔画》

目录

一,一笔画三定律

二,一笔画2

1,简单图

2,带二重平行边的图

3,带1个传送门的图

4,带绿线的图

4.1,基于闭环的分割

4.2,完全分割图

4.3,对称性

4.4,基于有向小闭环的消去原理

4.5,基于传送门的分割

4.6,基于邻居的出度和入度估计法(类似贪心算法)

4.7,优先级原则

4.8,基于绿线的分割

三,OJ实战

CCF-CSP-2015-12-4 送货

POJ 1300 Door Man

POJ 1386 Play on Words


一,一笔画三定律

一笔画第一定律:对于无向连通图,如果奇点数量为0或者2,则可以一笔画,否则不能一笔画。

一笔画第二定律:对于无向连通图,如果奇点个数为2,那么2个奇点可以任选1个作为起点,其他点不能作为起点,如果奇点个数为0,那么任何一个点都可以作为起点。

一笔画第三定律:对于无向连通图,起点由第二定律决定,在一笔画的过程中,只要始终保持所有的边是连通的,最终一定能完成一笔画。

二,一笔画2

这篇文章,我想分享一下自己的经验,关于如何快速找到起点。

APK下载链接:资源分享汇总_nameofcsdn的博客-CSDN博客_上海各区gm资源汇总

规则介绍:

这个游戏是要一笔画完整个图,由于起点为给出,所以需要自己寻找。

如果一开始起点就选错了,那么无论如何都无法过关了。

这个游戏,是难度递增的,可以分成四大类。

第一种,是只有蓝线的(有的上面还有箭头),也就是简单图。

第二种,有了红线(有的上面还有箭头),红线其实就是二重平行边。

第三种,有了传送门,传送门是2个黄点组成的一对。(图中只有1个传送门,也只有2个黄点)

第四种,有了绿线,绿线都是带箭头的,而且其中一端是绿点。每次经过绿点,绿线的方向就会改变。

下面分这四种类型,分别介绍如何寻找起点。

1,简单图

简单图有2种,一种是没有奇点的:

 

无论是有向图还是无向图,一定是一个闭环,那么任何点都可以用作起点。

另外一种是有2个奇点的:    

2个奇点必定是起点和终点。

在无向图中,任意一个做起点,另外一个做终点,都可以。

     

在有向图中,有的像图12一样,2个点都可以作为起点,有的像图14一样,只有1个点能作为起点。

对于有唯一起点的情况,要想找出起点,还需要下面的定理。

定理一:在有2个奇点的有向图中,除了2个奇点之外,所有的点都满足入度==出度,

而这2个奇点中,起点满足入度==出度-1,终点满足入度==出度+1(证明略~~~)

图14中这个定理的使用:

分析点3就可以发现,点3到点2之间的线的方向是2→3而不是3→2,如图。

同理,分析点4可以发现是4→2,分析点5可以发现是2→5。

那么,点2即为起点,点1即为终点。

2,带二重平行边的图

红线的意思是,这条线必须经过2次,其实也就是二重平行边,1条红边等价于2条平行的蓝边。 

理解什么叫二重平行边之后,问题就可以轻松地等价转化成前面的简单图的问题了。

如图22中,点1和点2是奇点,而且点1的出度至少为2,所以点1是起点,点2是终点。

3,带1个传送门的图

定理二:

对于2个传送门的2个端点,即黄点A和B,A的入度减B的出度一定是0或者1或者是-1

如果是1,那么A是终点,如果是-1,那么B是起点。

对于A和B之外的点,如果是奇点,就一定是起点或终点,对于A和B就不一定了。

定理二的推论:

A和B的度一定相差为2或者1或者0

1,如果相差是2,那么度大的那个点既是起点,又是终点,除了A和B之外没有奇点。

2,如果相差是1,那么度大的那个点即为起点或终点,除了A和B之外恰有1个奇点,这个点也是起点或终点。

如图33,因为点1的入度至少为2,所以点1是终点,点3是起点。

3,如果相差是0,那么又有2种情况:

(1)除了A和B之外恰有2个奇点,那么这2个点就是起点和终点

(2)除了A和B之外没有奇点,那么整个图一定是闭环,任何点都可以作为起点。

 

4,带绿线的图

绿线本质上是一条有向边,只不过方向不好确定。

所以说,以上提到的所有内容,对于带绿线的图同样成立。对于简单的情况,用上述内容即可解决。

但是,对于大部分情况,上述内容无法得到结果,因为绿线不是无向边,必须按照绿箭头的方向走,而绿箭头的方向却又总是变,所以定量的分析效果不大。

所以接下来,我会列举一些经典的情形,来给出一些方法。实际上,这些方法也可以用到无绿线的图中,效果一样很好。

4.1,基于闭环的分割

(不一定保证对,但目前还没有出错过,而且有奇效)

把图分割成2个子图,一个图是闭环(多个闭环合起来还是闭环),另外一个图包含绿线。

让包含绿线的图尽量简洁,然后优先解决这个图,最后只要解决这个闭环就可以了。

例1:

例2:

例3:

  

(红线不是闭环,但是红线加闭环还是闭环)

例4:

  

例5:

 

对于图133,有2个点是奇点,但是不知道哪个是起点。

定理三:

如果一个图可以分成2个子图,这2个子图只有2个公共点(都不是黄点),而且这2个点就是起点和终点,那么这2个子图一定有1个是闭环。

根据这个定理,图133虽然有2个黄点,但是起点和终点都不是黄点,而且可以分成上下2个子图,所以上面的子图一定是闭环,所以只需要先解决下面的子图。

一眼看过去,很难看出上面的子图是闭环,不过确实是闭环。

4.2,完全分割图

随便命名的,就是指这种如果不考虑传送门的效果的话,2个部分完全分割开来的图。

  

如果其中有1个部分是不自相交的闭环的话,那就太简单了,起点一定在另外1个部分。

(考虑到整个图为闭环的情况,更严谨的说法应该是,起点一定可以是另外1个部分上面的点)

在另外1个部分找起点的时候,可以忽略掉传送门,这样就很简单了。

4.3,对称性

这2个图都有着比较强的对称性,但不是完全对称。

2个都是,2个奇点都可以作为起点

4.4,基于有向小闭环的消去原理

其实这个和基于闭环的分割差不多,一步步挖掘出小闭环,必须是有向的。

   

图109可以分成5个子图,4个有向小闭环都消去之后,就只剩1条简单的路径了。

4.5,基于传送门的分割

根据传送门,分割成若干块,每一块都相当于若干条连接2个传送门的线段。

  

很明显,她们是有主次之分的。
一般来说,主分区只有1个,其他都是次分区。
次分区的特点很明显,简单,无限制或者限制很少(限制主要指箭头)。
当然,还有一种特殊的情况,箭头特别多,但是全部指向同一个传送门,这也是一种很有趣的闭环。
最重要的是,次分区没有奇点,可以构成闭环。
这样看起来,每个分区都可以轻松解决掉。
主分区是一条路径,除了起点和终点之外,至少有1个点是传送门,所以主分区是由2部分构成,起点→传送门,传送门→终点。
所以完整的路径是:起点→传送门,第一个次分区,第二个次分区......最后一个次分区,传送门→终点。

4.6,基于邻居的出度和入度估计法(类似贪心算法)

我之所以总结出这样一个方法,实在是因为上面的方法对下图120几乎无效,图120实在是难的离谱。

点和边的位置关系可以分4类:
第一种,点是边的2个端点之一
第二种,点和边的2个端点都是邻居
第三种,点和边的2个端点,一个是邻居,一个不是邻居
第四种,点和边的2个端点都不是邻居

基于邻居的出度和入度估计法:只考虑和奇点形成第一种或者第三种位置关系的有向边,计算所有这样的边对奇点的入度或者出度的贡献的总和,然后比较2个奇点得到起点和终点

首先,点1和点2是奇点,ABCD四条边都是有向边。

对于点1,只考虑ABC,对于点2,只考虑D

然后计算贡献
对于点1    A:入度+2    B:出度+1     C:出度+1    合计:贡献为0
对于点2    D:入度+2    合计:贡献为入度加2

所以,2是终点,1是起点。 

注意:当2个奇点的计算值相差为2或者超过2,往往非常可信,相差为1甚至0的话就不是特别可信了。

这里面还要注意绿线的问题,如果绿线的方向可以预先确定的话,绿线也可以参与计算贡献,但是如果绿线的方向无法确定,或者特别麻烦,懒得分析这个,那绿线可以不参与计算贡献。

再看2个例子:

很明显点1和点2是起点和终点。
点1的计算值为0,点2的计算值为出度加2,所以点2为起点。

很明显点1和点2是起点和终点。
点1的计算值为0,点2的计算值为入度加1,所以点1为起点。

4.7,优先级原则

原则一,对于能够确定方向的绿线,优先干掉绿线
原则二,优先级,有向红边>有向蓝边>红边>蓝边

4.8,基于绿线的分割

有些图是由子图+绿线+子图构成的,即绿线是连接2个子图的唯一的边。

这里的子图,指的是不包含传送门或者包含2个传送门的图。

         

开局的时候绿箭头的方向一定就是最后通过绿线的方向,所以绿点所在的那个子图一定包含起点。

(这个子图包含绿点)

三,OJ实战

CCF-CSP-2015-12-4 送货

题目:

问题描述

  为了增加公司收入,F公司新开设了物流业务。由于F公司在业界的良好口碑,物流业务一开通即受到了消费者的欢迎,物流业务马上遍及了城市的每条街道。然而,F公司现在只安排了小明一个人负责所有街道的服务。
  任务虽然繁重,但是小明有足够的信心,他拿到了城市的地图,准备研究最好的方案。城市中有n个交叉路口,m条街道连接在这些交叉路口之间,每条街道的首尾都正好连接着一个交叉路口。除开街道的首尾端点,街道不会在其他位置与其他街道相交。每个交叉路口都至少连接着一条街道,有的交叉路口可能只连接着一条或两条街道。
  小明希望设计一个方案,从编号为1的交叉路口出发,每次必须沿街道去往街道另一端的路口,再从新的路口出发去往下一个路口,直到所有的街道都经过了正好一次。

输入格式

  输入的第一行包含两个整数n, m,表示交叉路口的数量和街道的数量,交叉路口从1到n标号。
  接下来m行,每行两个整数a, b,表示和标号为a的交叉路口和标号为b的交叉路口之间有一条街道,街道是双向的,小明可以从任意一端走向另一端。两个路口之间最多有一条街道。

输出格式

  如果小明可以经过每条街道正好一次,则输出一行包含m+1个整数p 1, p 2, p 3, ..., p m +1,表示小明经过的路口的顺序,相邻两个整数之间用一个空格分隔。如果有多种方案满足条件,则输出字典序最小的一种方案,即首先保证p 1最小,p 1最小的前提下再保证p 2最小,依此类推。
  如果不存在方案使得小明经过每条街道正好一次,则输出一个整数-1。

样例输入

4 5
1 2
1 3
1 4
2 4
3 4

样例输出

1 2 4 1 3 4

样例说明

  城市的地图和小明的路径如下图所示。

样例输入

4 6
1 2
1 3
1 4
2 4
3 4
2 3

样例输出

-1

样例说明

  城市的地图如下图所示,不存在满足条件的路径。

评测用例规模与约定

  前30%的评测用例满足:1 ≤ n ≤ 10, n-1 ≤ m ≤ 20。
  前50%的评测用例满足:1 ≤ n ≤ 100, n-1 ≤ m ≤ 10000。
  所有评测用例满足:1 ≤ n ≤ 10000,n-1 ≤ m ≤ 100000。

代码:

#include <iostream>
#include<queue>
#include<list>
#include<stack>
#include<algorithm>
#include<stdio.h>
using namespace std;
 
list<int>edge[10001];
int n;
stack<int>ans;
 
int num_of_sin()	//求奇点的个数,是0,2,还是大于2
{
	int ans = 0;
	for (int i = 1; i <= n && ans<=2; i++)if (edge[i].size() % 2)ans++;
	return ans;
}
 
bool con()	//判断是否为连通图
{
	int *p = new int[n + 1];
	for (int j = 1; j <= n; j++)p[j] = 0;
	queue<int>q;
	while (!q.empty())q.pop();
	p[1] = 1, q.push(1);
	while (!q.empty())
	{
		int k = q.front();
		q.pop();
		for (list<int>::iterator it = edge[k].begin(); it != edge[k].end(); it++)if (p[*it] == 0)
		{
			p[*it] = 1;
			q.push(*it);
		}
	}
	for (int j = 1; j <= n; j++)if (p[j] == 0)return false;
	return true;
}
 
bool ok()	//判断能不能一笔画
{
	if (num_of_sin() > 2 || !con())return false;
	if (num_of_sin() == 0)return true;
	return edge[1].size() % 2;
}
 
bool dfs(int s, int deep)
{
	if (edge[s].empty())return deep == 0;
	int e = *min_element(edge[s].begin(), edge[s].end());
	edge[s].remove(e), edge[e].remove(s);
	if (dfs(e, deep - 1))
	{
		ans.push(e);
		return true;
	}
	if (edge[s].empty())
	{
		edge[s].push_back(e), edge[e].push_back(s);
		return false;
	}
	int t = *min_element(edge[s].begin(), edge[s].end());
	edge[s].push_back(e), edge[e].push_back(s);
	e = t;
	edge[s].remove(e), edge[e].remove(s);
	if (dfs(e, deep - 1))
	{
		ans.push(e);
		return true;
	}
	edge[s].push_back(e), edge[e].push_back(s);
	return false;
}
 
int main()
{
	int m, a, b, s = 1;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)edge[i].clear();
	for (int i = 0; i < m; i++)
	{
		scanf("%d%d", &a, &b);
		edge[a].push_back(b), edge[b].push_back(a);
	}
	if (!ok())
	{
		cout << -1;
		return 0;
	}
	while (!ans.empty())ans.pop();
	dfs(1, m);
	cout << 1;
	while (!ans.empty())
	{
		printf(" %d", ans.top());
		ans.pop();
	}
	return 0;
}

POJ 1300 Door Man

Description

You are a butler in a large mansion. This mansion has so many rooms that they are merely referred to by number (room 0, 1, 2, 3, etc...). Your master is a particularly absent-minded lout and continually leaves doors open throughout a particular floor of the house. Over the years, you have mastered the art of traveling in a single path through the sloppy rooms and closing the doors behind you. Your biggest problem is determining whether it is possible to find a path through the sloppy rooms where you:

  1. Always shut open doors behind you immediately after passing through
  2. Never open a closed door
  3. End up in your chambers (room 0) with all doors closed


In this problem, you are given a list of rooms and open doors between them (along with a starting room). It is not needed to determine a route, only if one is possible.

Input

Input to this problem will consist of a (non-empty) series of up to 100 data sets. Each data set will be formatted according to the following description, and there will be no blank lines separating data sets.
A single data set has 3 components:

  1. Start line - A single line, "START M N", where M indicates the butler's starting room, and N indicates the number of rooms in the house (1 <= N <= 20).
  2. Room list - A series of N lines. Each line lists, for a single room, every open door that leads to a room of higher number. For example, if room 3 had open doors to rooms 1, 5, and 7, the line for room 3 would read "5 7". The first line in the list represents room 0. The second line represents room 1, and so on until the last line, which represents room (N - 1). It is possible for lines to be empty (in particular, the last line will always be empty since it is the highest numbered room). On each line, the adjacent rooms are always listed in ascending order. It is possible for rooms to be connected by multiple doors!
  3. End line - A single line, "END"


Following the final data set will be a single line, "ENDOFINPUT".

Note that there will be no more than 100 doors in any single data set.

Output

For each data set, there will be exactly one line of output. If it is possible for the butler (by following the rules in the introduction) to walk into his chambers and close the final open door behind him, print a line "YES X", where X is the number of doors he closed. Otherwise, print "NO".

Sample Input

START 1 2
1

END
START 0 5
1 2 2 3 3 4 4




END
START 0 10
1 9
2
3
4
5
6
7
8
9

END
ENDOFINPUT

Sample Output

YES 1
NO
YES 10

思路:这就是个含多重边的无向图,给定起点m终点0,问是否能一笔画的问题。

本来应该满足连通性和出入度奇偶性2个条件才能正确判断,不过这个的测试数据比较水,我就省掉了判断连通性了。

#include <iostream>
#include <string>
#include <vector>
#include <time.h>
#include <functional>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <string.h>
#include <math.h>
#include <fstream>
#include <stdio.h>
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
#include <stack>
#include <set>
#include <queue>
#include <math.h>
using namespace std;

......

bool pointOk(map<int, int>&nums, int a, int b,int low,int high)
{
	if (a != b && (nums[a] % 2 == 0 || nums[b] % 2 == 0))return false;
	for (int i = low; i <= high; i++) {
		if (i == a || i == b)continue;
		if (nums[i] % 2 != 0)return false;
	}
	return true;
}

int main()
{
	string s;
	int m;
	int n;
	map<int, int>nums;
	while (cin >> s) {
		if (s == "ENDOFINPUT")break;
		cin >> m >> n;
		getline(cin, s);
		nums.clear();
		int edges = 0;
		for (int i = 0; i < n; i++) {
			getline(cin, s);
			if (s.length() > 0) {
				vector<string> v = StringSplit(s);
				for (int j = 0; j < v.size(); j++) {
					int x = strToInt(v[j].data(), 10);
					nums[i]++;
					nums[x]++;
					edges++;
				}
			}
		}
		cin >> s;
		if (!pointOk(nums, m, 0, 0, n - 1)) {
			cout << "NO\n";
			continue;
		}
		cout << "YES " << edges << endl;
	}
	return 0;
}

POJ 1386 Play on Words

Description

Some of the secret doors contain a very interesting word puzzle. The team of archaeologists has to solve it to open that doors. Because there is no other way to open the doors, the puzzle is very important for us.

There is a large number of magnetic plates on every door. Every plate has one word written on it. The plates must be arranged into a sequence in such a way that every word begins with the same letter as the previous word ends. For example, the word ``acm'' can be followed by the word ``motorola''. Your task is to write a computer program that will read the list of words and determine whether it is possible to arrange all of the plates in a sequence (according to the given rule) and consequently to open the door.

Input

The input consists of T test cases. The number of them (T) is given on the first line of the input file. Each test case begins with a line containing a single integer number Nthat indicates the number of plates (1 <= N <= 100000). Then exactly Nlines follow, each containing a single word. Each word contains at least two and at most 1000 lowercase characters, that means only letters 'a' through 'z' will appear in the word. The same word may appear several times in the list.

Output

Your program has to determine whether it is possible to arrange all the plates in a sequence such that the first letter of each word is equal to the last letter of the previous word. All the plates from the list must be used, each exactly once. The words mentioned several times must be used that number of times.
If there exists such an ordering of plates, your program should print the sentence "Ordering is possible.". Otherwise, output the sentence "The door cannot be opened.".

Sample Input

3
2
acm
ibm
3
acm
malform
mouse
2
ok
ok

Sample Output

The door cannot be opened.
Ordering is possible.
The door cannot be opened.
#include <iostream>
#include <string>
#include <vector>
#include <time.h>
#include <functional>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <string.h>
#include <math.h>
#include <fstream>
#include <stdio.h>
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
#include <stack>
#include <set>
#include <queue>
#include <math.h>
using namespace std;

class Union{......}

bool pointOk(map<char, int>&m1, map<char, int>&m2)	//求奇点的个数,是0,2,还是大于2
{
	int a = 0, b = 0;
	for (char c = 'a'; c <= 'z'; c++) {
		int d = m1[c] - m2[c];
		if (d == 0)continue;
		else if (d == 1)a++;
		else if (d == -1)b++;
		else return false;
	}
	return a < 2 && b < 2;
}


int main()
{
	ios::sync_with_stdio(false);
	int t;
	cin >> t;
	while (t--) {
		int n;
		cin >> n;
		string s;
		map<char, int>m1;
		map<char, int>m2;
		char a, b;
		Union un(26);
		while (n--) {
			cin >> s;
			a = s[0];
			b = s[s.length() - 1];
			m1[a]++;
			m2[b]++;
			un.merge(a - 'a', b - 'a');
		}
		if (pointOk(m1, m2)) {
			bool flag = true;
			for (char c = 'a'; c <= 'z'; c++) {
				if (c == a)continue;
				if (m1[c] == 0 && m2[c] == 0)continue;
				if (!un.inSame(a - 'a', c - 'a'))flag = false;
			}
			if(flag)cout << "Ordering is possible.\n";
			else cout << "The door cannot be opened.\n";
		}
		else {			
			cout << "The door cannot be opened.\n";
		}
	}
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单词搜索迷宫(Word Search Puzzle)问题是一个经典的算法问题,其输入是一个二维的字符数组和一组单词,目标是找出字符数组网格中的所有单词。这些单词可以是水平的、垂直的或者是任意的对角线方向,所以需要查找8个不同的方向。解决这个问题的一种常见方法是使用回溯算法,具体步骤如下: 1. 遍历二维字符数组,对于每个字符,以其为起点开始搜索,搜索的方向包括水平、垂直和对角线方向。 2. 对于每个搜索到的单词,将其记录下来。 3. 重复步骤1和2,直到遍历完整个二维字符数组。 下面是一个使用C#语言实现的单词搜索迷宫算法的示例代码: ```csharp class WordSearchPuzzle { private char[,] grid; private HashSet<string> words; public WordSearchPuzzle(char[,] grid, HashSet<string> words) { this.grid = grid; this.words = words; } public void Solve() { int rows = grid.GetLength(0); int cols = grid.GetLength(1); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { Search(i, j, new StringBuilder()); } } } private void Search(int row, int col, StringBuilder sb) { if (row < 0 || row >= grid.GetLength(0) || col < 0 || col >= grid.GetLength(1)) { return; } sb.Append(grid[row, col]); string word = sb.ToString(); if (words.Contains(word)) { Console.WriteLine("Found '{0}' at [{1}, {2}] to [{3}, {4}]", word, row, col, row - sb.Length + 1, col - sb.Length + 1); } if (word.Length < 3) { Search(row + 1, col, sb); Search(row - 1, col, sb); Search(row, col + 1, sb); Search(row, col - 1, sb); Search(row + 1, col + 1, sb); Search(row - 1, col - 1, sb); Search(row + 1, col - 1, sb); Search(row - 1, col + 1, sb); } sb.Remove(sb.Length - 1, 1); } } // 使用示例 char[,] grid = new char[,] { {'t', 'h', 'i', 's'}, {'w', 'a', 't', 's'}, {'o', 'a', 'h', 'g'}, {'f', 'g', 'd', 't'} }; HashSet<string> words = new HashSet<string>() { "this", "two", "fat", "that" }; WordSearchPuzzle puzzle = new WordSearchPuzzle(grid, words); puzzle.Solve(); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值