《算法设计编程实验:大学程序设计课程与竞赛训练教材》——2.2 筛选法模拟的实验范例

简介: 本节书摘来自华章计算机《算法设计编程实验:大学程序设计课程与竞赛训练教材》一书中的第2章,第2.2节,作者:吴永辉,王建德著, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.2 筛选法模拟的实验范例

模拟过程中可能产生的所有解组成一个筛。筛选法模拟即先从题意中找出约束条件,然后将筛中的每一个可能解放到约束条件的过滤器上,一次一次地将不符合条件的解过滤掉,最后沉淀在筛中的即为问题的解。
筛选法模拟的结构和思路简明、清晰,但带有盲目性,因此时间效率并不一定令人满意。筛选法模拟的关键是找准约束条件,任何错误和疏漏都会导致模拟失败。
【2.2.1 The Game】
【问题描述】
五子棋游戏是由两名玩家在一个19*19的棋盘上玩的游戏。一名玩家执黑,另一名玩家执白。游戏开始时棋盘为空,两名玩家交替放置黑色棋子和白色棋子。执黑者先走。棋盘上有19条水平线和19条垂直线,棋子放置在直线的交点上。
水平线从上到下标记为1,2,…,19,垂直线从左至右标记为1,2,…,19。
这一游戏的目标是把5个相同颜色的棋子沿水平、垂直或对角线连续放置。所以,在图2.2-1中执黑的一方获胜。但是,如果一个玩家将超过五个相同颜色的棋子连续放置,也不能判赢。 

image


基于这一游戏的棋盘情况,请写一个程序,确定是白方赢了比赛,还是黑方赢了比赛,或者是还没有任何一方赢了比赛。输入数据保证不可能出现黑方和白方都赢的情况,也没有白方或黑方在多处获胜的情况。
输入:
输入的第一行包含一个整数t(1≤t≤11),表示测试用例的数目。接下来给出每个测试用例,每个测试用例19行,每行19个数,黑棋子标识为1,白棋子标识为2,没有放置棋子则标识为0。
输出:
对每个测试用例,输出一行或两行。在测试用例的第一行输出结果,如果黑方获胜,则输出1;如果白方获胜,则输出2;如果没有任何一方能获胜,则输出0。如果黑方或白方获胜,则在第二行给出在5个连续的棋子中最左边棋子的水平线编号和垂直线编号(如果5枚连续的棋子垂直排列,则选最上方棋子的水平线编号和垂直线编号)。
image

试题来源:ACM Tehran Sharif 2004 Preliminary
在线测试地址:POJ 1970,ZOJ 2495
试题解析
初始时所有棋子组成一个筛子。我们由上而下、由左而右扫描每个棋子,分析其k方向的相邻棋子(0≤k≤3,0≤i,j≤18,见图2.2-2)。 

image


过滤器中“赢”的约束条件是:
1)(i,j)k的相反方向的相邻格(x,y)不同色。
2)(i,j)k方向延伸5格在界内。
3)从(i,j)开始,沿k方向连续5格同色且第6格不同色。
若(i,j)的棋子满足上述约束条件,其颜色所代表的一方赢,(i,j)即为赢方5个连续的同色棋子中首枚棋子的位置;若检测了4个方向,(i,j)的棋子依然不满足约束条件,则被过滤掉。
若过滤了筛中的所有棋子后筛子变空,则说明没有任何一方能获胜。
程序清单
#include <iostream>
using namespace std;

const int d[4][2] = {{0, 1}, {1, 0}, {1, 1}, {-1, 1}};  // 4个方向的位移增量

inline bool valid(int x, int y)// 返回(x,y)在界内的标志
{
return x >= 0 && x < 19 && y >= 0 && y < 19;
}

int a[20][20];// 五子棋盘

int main()
{
int i, j, k, t, x, y, u;
scanf("%d", &t);// 输入测试用例数
while (t--)// 反复输入测试用例
{
for (i = 0; i < 19; ++i)// 输入五子棋盘
for (j = 0; j < 19; ++j) scanf("%d", &a[i][j]);
for (j = 0; j < 19; ++j)// 由左而右、由上而下扫描每个有棋子的
// 位置(i,j)
{
for (i = 0; i < 19; ++i)
{
if (a[i][j] == 0) continue;
for (k = 0; k < 4; ++k)// 枚举4个方向
{// 过滤:若(i,j)k的相反方向的相邻格
// (x,y)同色,则换一个方向;若(i,j)k方向延伸5格越出界外,则换一个方向;若(i,j)k方向的连续6个格子同色,
// 则换一个方向
x = i - d[k][0];y = j - d[k][1];
if (valid(x, y) && a[x][y] == a[i][j]) continue;
x = i + d[k][0] *4;y=j + d[k][1] *4;
if (!valid(x, y)) continue;
for (u = 1; u < 5; ++u)
{
x = i + d[k][0] *u;y = j + d[k][1] *u;
if (a[x][y] != a[i][j]) break;
}
x = i+d[k][0]*5;y = j+d[k][1]*5;
if (valid(x, y) && a[x][y] == a[i][j]) continue;
if (u == 5) break;
}
if (k < 4) break;
}
if (i < 19) break;
}

if (j < 19)// 若(i,j)在某方向上存在连续5个同色
// 格,则该色一方赢;若扫描了所有格子仍未出现任何方向上有连续5个同色格的情况,则没有任何一方能获胜
{
printf("%d\n", a[i][j]);// 输出赢方的标志和5个连续棋子的起始
// 位置
printf("%d %d\n", i + 1, j + 1);
}
else puts("0");// 输出没有任何一方能获胜的信息
}
return 0;
}

【2.2.2 Game schedule required】
【问题描述】
Sheikh Abdul真正热爱足球,所以你最好不要问他为著名的球队进入年度锦标赛花了多少钱。当然,他花了这么多钱,就是想看到某些球队彼此间的比赛。他拟定了他想看到的所有比赛的完整列表。现在请按以下规则将这些比赛分配到某些淘汰赛的轮次中:
在每一轮中,晋级的每支球队最多只进行一场比赛。
如果有偶数支晋级这一轮的球队,那么每支球队只进行一场比赛。
如果有奇数支晋级这一轮的球队,那么恰好有一支球队没有进行比赛(它优先用外卡晋级下一轮)。
每场比赛的优胜者晋级下一轮,失败者被淘汰出锦标赛。
如果只有一支球队,那么这支球队就被宣布为锦标赛的优胜者。
可以证明,如果有n支球队参加锦标赛,那么直至产生比赛优胜者,恰好有n-1场比赛。显然,在第一轮后,有的应该要参加下一轮比赛的球队可能会被淘汰,为了防止这种情况,对于每场比赛,还必须知道哪支球队会赢。
输入:
输入包含若干测试用例,每个测试用例首先给出一个整数n(2≤n≤1000),表示参加锦标赛的球队的数目。然后的n行给出参加锦标赛的球队的队名。本题设定每个球队的队名可以由多达25个英文字母的字符组成('a'~'z'或'A'~'Z')。
接下来的n-1行给出Sheikh想要看的比赛(按任何顺序)。每行由要进行比赛的两支球队的队名组成。本题设定总是可以找到一个包含给出比赛的锦标赛日程。
测试用例结束后,给出一个0。
输出:
对于每个测试用例,输出一个分布在多个轮次中的比赛日程。
对于每一轮,首先在一行中输出"Round #X"(其中X表示第几轮),然后输出在这一轮中的比赛形式:"A defeats B",其中A是晋级队的队名,B是被淘汰队的队名。如果在这一轮需要外卡,则在这一轮最后一场比赛后,输出"A advances with wildcard",其中A是获得外卡的球队的队名。在最后一轮之后,按如下格式输出优胜队,在每个测试用例之后输出一个空行。
image

试题来源:Ulm Local 2005
在线测试地址:POJ 2476,ZOJ 2801
试题解析
可能的赢方组成一个筛子,初始时每个球队在筛中。
我们首先计算Sheikh想要看的n-1场比赛中每场比赛的两个球队编号a[i]和b[i](1≤i≤n-1),累计每个球队比赛的场次数cnt[i](1≤i≤n)。然后从第一轮次开始,计算分布在每个轮次中比赛的日程。由于每一轮的比赛都是Sheikh想要看的,因此若当前比赛的两个球队中仅有一个球队没有比赛完,则该球队赢。由此得出过滤器中“保留球队”的约束条件:
1)Sheikh想要看的比赛中的两个球队不在当前轮次。
2)当前轮次Sheikh想要看的比赛中需要进入下一轮的球队。
满足上述条件的球队留在筛中。在进入第一轮前,所有球队都在筛中。每个轮次的比赛场次数为进入该轮次前筛中的球队数2。
反复进行如下过滤,直至筛中仅剩一支球队为止:
顺序搜索Sheikh想要看的每场比赛i(1≤i≤n-1):
若a[i]和b[i]在筛中,且两队中至少有一支队伍只能比一场,则这两个球队比赛1场,两队剩余的比赛场次数-1;在筛中滤去这两个球队(避免球队在当前轮次比赛两场)。若仅存在1个可进入下一轮的球队,则该队赢本场比赛;若两队都不能进入下一轮,则产生锦标赛的优胜者。
若比赛完当前轮次的所有场次,则新增一个轮次,新轮次的比赛场次数为筛中的球队数2,向筛中还有比赛的球队发放外卡,筛外未比赛完的球队重新回到筛里,以进入新一轮。
程序清单

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
using namespace std;
const int maxN=1010;
int n,a[maxN],b[maxN],cnt[maxN];            // 球队数为n; Sheikh想要看的第i(1≤
// i≤n-1)场比赛的两支队伍为a[i]和b[i],球队k(1≤k≤n)尚需比赛的场次数为cnt[k]
char name[maxN][30];// 每支队伍的名字
bool flag[maxN];// 球队在筛中的标志
map<string,int> que;// 将每支队伍按顺序依次编号

bool cmp(int a,string s)// 判断编号为a的队伍的名字串是否为s
{
for (int i=0;i<s.size();i++)
if (name[a][i]!=s[i]) return false;
return true;
}


void init()// 输入n支球队和Sheikh想要看的n-1
// 场比赛的信息
{
que.clear();
for (int i=1;i<=n;i++)// 输入每支球队的名称
{
scanf("%s",name[i]);
que.insert(map<string,int>::value_type(name[i],i));// 建立球队名称与编号的对应关系
}
string s;
int p;
char ch;scanf("%c",&ch);// 将Sheikh想要看的比赛的两支队伍分别
// 记入a和b中
for (int i=1;i<n;i++)// 输入Sheikh想要看的n-1场比赛,输入
// 每场比赛的两支球队,计算各场的队伍编号a[]和b[],累计队伍的比赛场次数
{
scanf("%c",&ch);s="";
while (ch!=' ') { s+=ch;scanf("%c",&ch);}
p=que[s];
cnt[p]++;a[i]=p;
scanf("%c",&ch);s="";
while (ch!='\n') { s+=ch;scanf("%c",&ch);}
p=que[s];
cnt[p]++;b[i]=p;
}
}

void work()// 计算和输出分布在多个轮次中的比赛日程
{
int rnd=1,tm=n,s=n/2,now=0;// rnd记录当前为第几轮,tm记录当前剩余
// 多少支队伍,s记录每轮需要比赛的场次数,now记录每轮已经比赛的场次数
memset(flag,1,sizeof(flag));// 初始时每个队都在筛中
while (tm!=1)// 当剩余1支球队时结束
for (int i=1;i<n;i++)// 搜索要观看的每次比赛
if (flag[a[i]]&&flag[b[i]]&&((cnt[a[i]]==1)||(cnt[b[i]]==1)))
// 若要观看的第i次比赛的两个队都在筛中,且至少有一支队伍只能比一场(这支队伍比完这场即被淘汰)
{
if (now==0)printf("Round #%d\n",rnd);// 若当前轮次刚开始,则输出轮次数
now++;tm--;// 当前轮次比赛的场次数+1,剩余的队伍数-1
cnt[a[i]]--;cnt[b[i]]--;// 两个队的比赛次数-1
// 若b[i]只能赛这一场,则a[i]晋级b[i]淘汰;若a[i]只能赛这一场,则b[i]晋级a[i]淘汰;若a[i]和b[i]都只
// 能赛这一场,则b[i]赢
if (cnt[a[i]]) printf("%s defeats %s\n",name[a[i]],name[b[i]]);
else if (cnt[b[i]]) printf("%s defeats %s\n",name[b[i]],name[a[i]]);
else{
printf("%s defeats %s\n",name[b[i]],name[a[i]]);
printf("Winner: %s\n",name[b[i]]);}
flag[a[i]]=false;flag[b[i]]=false;// 两队从筛中滤去

if (now==s)// 若当前轮次结束所有场比赛,则设置下一
// 轮次应比赛的场次数s,已经比赛的场次数清零,并寻找是否有队伍落单
{
now=0;rnd++;s=tm/2;
for (int i=1;i<=n;i++)// 搜索每个球队,若在筛中且未比赛完,则
// 向该队发放外卡;若筛外有未比赛完的球队,则重新进入筛中
{
if (flag[i] && cnt[i])
printf("%s advances with wildcard\n",name[i]);
if (cnt[i]) flag[i]=true;else flag[i]=false;
}
}
}
printf("\n");
}

int main()
{
while (scanf("%d",&n),n)// 输入球队数,直至输入0为止
{
init();// 输入n支球队和Sheikh想要看的n-1
// 场比赛的信息
work();// 计算和输出分布在多个轮次中的比赛日程
}
return 0;
}
相关文章
|
4月前
|
算法
2017级《算法设计与分析》--实验1--分治算法-骨牌铺方格
2017级《算法设计与分析》--实验1--分治算法-骨牌铺方格
|
4月前
|
算法
2017级《算法设计与分析》--实验1--分治算法
2017级《算法设计与分析》--实验1--分治算法
|
6月前
|
存储 算法 C语言
C语言---数据结构实验---哈夫曼树及哈夫曼编码的算法实现---图的基本操作
C语言---数据结构实验---哈夫曼树及哈夫曼编码的算法实现---图的基本操作
|
1月前
|
机器学习/深度学习 数据采集 监控
机器学习-特征选择:如何使用递归特征消除算法自动筛选出最优特征?
机器学习-特征选择:如何使用递归特征消除算法自动筛选出最优特征?
56 0
|
4月前
|
算法 测试技术 编译器
【算法 | 实验18】在字符矩阵中查找给定字符串的所有匹配项
题目描述 题目 在字符矩阵中查找给定字符串的所有匹配项 给定一个M×N字符矩阵,以及一个字符串S,找到在矩阵中所有可能的连续字符组成的S的次数。所谓的连续字符,是指一个字符可以和位于其上下左右,左上左下,右上右下8个方向的字符组成字符串。用回溯法求解。
32 1
|
4月前
|
机器学习/深度学习 算法
【算法 | 实验7】以最小的步骤收集所有硬币(算法正确性还没想清楚)
题目 最小步骤收集硬币 有许多相邻排列的硬币堆。我们需要以最少的步骤收集所有这些硬币,在一个步骤中,我们可以收集一个水平线的硬币或垂直线的硬币,收集的硬币应该是连续的。 输入描述 输入第一行整数N表示硬币堆的数量
35 0
|
4天前
|
存储 机器学习/深度学习 算法
上机实验三 图的最小生成树算法设计 西安石油大学数据结构
上机实验三 图的最小生成树算法设计 西安石油大学数据结构
15 1
|
2月前
|
存储 缓存 网络协议
计算机网络:思科实验【3-集线器与交换机的区别、交换机的自学习算法】
计算机网络:思科实验【3-集线器与交换机的区别、交换机的自学习算法】
|
4月前
|
机器学习/深度学习 算法 C++
【算法 | 实验6-1】n*n的网格,从左上角开始到右下角结束遍历所有的方块仅一次,总共有多少种不同的遍历路径
前言 思路介绍中省略了关于如何进行回溯搜索的细节,而主要讨论回溯中所使用的剪枝策略。
59 0
|
4月前
|
tengine 人工智能 算法
极智AI | 量化实验分享四:Data-Free Quantization香不香?详解高通DFQ量化算法实现
大家好,我是极智视界,本文剖析一下高通 DFQ (Data-Free Quantization) 量化算法实现,以 Tengine 的实现为例。
77 1

热门文章

最新文章