题目

题目描述

在一个二维平面上有N个点,这N个点是(N/2)个虫洞的端点,虫洞的特点就是,你以什么状态从某个端点进去,就一定会以什么状态从另一端的端点出来。现在有一头牛总是沿着与X轴正方向平行的直线前进(也就是总是向右边走),所以它有可能被某些虫洞给困住。例如有一个虫洞的两个端点是A(0,0)、B(1,0),那么它如果从AB之间的某点开始出发,那么它一定会进入B点,但是当它进入B点之后会立刻传送到A点,这个时候它的朝向是不会变的,也就是说还是朝着X轴正方向,那么如果它继续前进,它肯定会又进入B点,然后就这样一直循环,被这个虫洞所困住。现在这个农夫不知道这头牛的位置(所以这头牛可以在平面上的任意一点),也不知道这个二维平面上的N个点是怎么连接的,所以你要找到所有的连接方式,并且记录有多少种方式,会让这头牛可能被虫洞困住。

数据范围

  1. 2 <= N <= 12,N为偶数
  2. 每个点的坐标的x,y都是属于0~100000000之间的正整数

样例输入

第一行输入N,下面N行输入N个端点的坐标

4
0 0
1 0
1 1
0 1

样例输出

2

解题思路

由于N很小,所以我们可以考虑直接枚举每一种可能的连接方式,然后再分别判断该种连接方式是否存在可以困住牛的虫洞,这也是最直接的想法。

我们可以计算一下枚举每一种连接方式的情况,如果N=12,那么我们会产生6条边,我们按照下面这种方式来找到这6条边:

  1. 将所有的点都编号,我们从编号为1的点开始连接,这个点有N-1个点可以与它连接。
  2. 我们将剩下的N-2个点中找到编号最小的点,从这个点开始连接,所以有N-3个点可以与它连接。
  3. 与上同理,我们可以一直处理到最后一条边。

这样我们可以计算(N-1)*(N-3)*(N-5)*……*3*1 = 10395,然后我们对每一种情况都必须遍历每一个点来判断是否能够困住这头牛,所以最终的结果是 10395 * 12这个结果约为100000,所以这个时间复杂度是可以接受的。

Tip:我们在判断是否能够被虫洞困住的时候一定要注意这样一个特点,那就是,“从一个端点A进入虫洞”与“从虫洞端点A出来”这两种状态是不一样的,例如这两个点A(0,0)、B(1,0)构成一个虫洞,我从B点进入,那么我肯定会被困住,但是如果我从B点出来,朝右走,那么这个虫洞是困不住我的。

解题代码

/*
ID: yinzong2
PROG: wormhole
LANG: C++11
*/
#define MARK
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<set>
#include<cstdlib>
#include<cstring>
using namespace std;
const int MAXN = 15;
const int INF = 0x3fffffff;
int n, ans; int pr[MAXN];
int right[MAXN];
bool vis[MAXN]; struct WormHole{
int x,y;
}w[MAXN]; set<int>s;
bool outFrom[MAXN]; //判断是否有环可以困住这头牛
bool testSolution() {
memset(outFrom, false, sizeof(outFrom));
for(int i = 1; i <= n; i++) {
if(!outFrom[i]) {
int start = i;
s.clear();//这个set是用来保存有哪些点在行进的过程中作为虫洞的出口,一定不能将虫洞的入口保存进去,保证一致性
s.insert(start);
outFrom[start] = true;
while(right[start]) { start = pr[ right[start] ];
if(s.find(start) != s.end()) {
return true;
} s.insert(start);//进入一个端点,和从这个端点出来的状态是不一样的,所以我们一定要注意
outFrom[start] = true;
}
}
}
return false;
} //枚举每一种可能的虫洞连接方式
void makeSolution(int start, int curLevel) {
if(curLevel == (n/2)) {
for(int i = 1; i <= n; i++) {
if(!vis[i] && start != i) {
pr[start] = i;
pr[i] = start;
}
}
if(testSolution()) {
ans++;
}
return ;
}
vis[start] = true;
for(int i = 1; i <= n; i++) {
if(i != start && !vis[i]) {
vis[i] = true;
pr[start] = i;
pr[i] = start;
for(int j = 1; j <= n; j++) {
if(!vis[j]) {
makeSolution(j, curLevel+1);
break; // 这里别忘记了
}
}
// 状态还原很重要
vis[i] = false;
}
}
// 状态还原很重要
vis[start] = false;
} //找到每个点的右边最近的点
bool findRight() {
int dis, id;
bool flag = false;
for(int i = 1; i <= n; i++) {
dis = INF;
for(int j = 1; j <= n; j++) {
if(i != j && w[i].y == w[j].y && w[i].x < w[j].x) {
if(dis > (w[j].x-w[i].x)) {
id = j;
dis = w[j].x-w[i].x;
}
}
}
if(dis != INF) {
right[i] = id;
flag = true;
}
}
return flag;
} int main() {
#ifdef MARK
freopen("wormhole.in", "r", stdin);
freopen("wormhole.out", "w", stdout);
#endif // MARK
while(~scanf("%d", &n)) {
for(int i = 1; i <= n; i++) {
scanf("%d%d", &w[i].x, &w[i].y);
right[i] = 0;
} bool flag = findRight(); ans = 0;
//优化,当所有的点的Y坐标都不相同的时候,肯定是没有解的。
if(flag) {
memset(vis, false, sizeof(vis));
makeSolution(1, 1);
} printf("%d\n", ans);
}
return 0;
}

官方给的题解还附带上youtube上的视频讲解,特别好。可以看到代码是怎么一步一步实现的。而且代码特别的优雅,值得好好学习。

#include <iostream>
#include <fstream>
using namespace std;
#define MAX_N 12 int N, X[MAX_N+1], Y[MAX_N+1];
int partner[MAX_N+1];
int next_on_right[MAX_N+1]; bool cycle_exists(void)
{
for (int start=1; start<=N; start++) {
// does there exist a cylce starting from start
int pos = start;
for (int count=0; count<N; count++)
pos = next_on_right[partner[pos]];
if (pos != 0) return true;
}
return false;
} // count all solutions
int solve(void)
{
// find first unpaired wormhole
int i, total=0;
for (i=1; i<=N; i++)
if (partner[i] == 0) break; // everyone paired?
if (i > N) {
if (cycle_exists()) return 1;
else return 0;
} // try pairing i with all possible other wormholes j
for (int j=i+1; j<=N; j++)
if (partner[j] == 0) {
// try pairing i & j, let recursion continue to
// generate the rest of the solution
partner[i] = j;
partner[j] = i;
total += solve();
partner[i] = partner[j] = 0;
}
return total;
} int main(void)
{
ifstream fin("wormhole.in");
fin >> N;
for (int i=1; i<=N; i++) fin >> X[i] >> Y[i];
fin.close(); for (int i=1; i<=N; i++) // set next_on_right[i]...
for (int j=1; j<=N; j++)
if (X[j] > X[i] && Y[i] == Y[j]) // j right of i...
if (next_on_right[i] == 0 ||
X[j]-X[i] < X[next_on_right[i]]-X[i])
next_on_right[i] = j; ofstream fout("wormhole.out");
fout << solve() << "\n";
fout.close();
return 0;
}

USACO Section 1.3 Wormholes 解题报告的更多相关文章

  1. USACO Section 1.2 Transformations 解题报告

    题目 题目描述 一块 N x N正方形的黑白瓦片的图案要被转换成新的正方形图案. 写一个程序来找出将原始图案按照以下列转换方法转换成新图案的最小方式: 转 90 度:图案按顺时针转 90 度. 转 1 ...

  2. USACO Section1.3 Wormholes 解题报告

    wormhole解题报告 —— icedream61 博客园(转载请注明出处)------------------------------------------------------------- ...

  3. USACO Section2.2 Preface Numbering 解题报告 【icedream61】

    preface解题报告----------------------------------------------------------------------------------------- ...

  4. USACO Section2.1 Hamming Codes 解题报告 【icedream61】

    hamming解题报告----------------------------------------------------------------------------------------- ...

  5. USACO Section2.1 Healthy Holsteins 解题报告 【icedream61】

    holstein解题报告 --------------------------------------------------------------------------------------- ...

  6. USACO Section2.1 The Castle 解题报告

    castle解题报告 —— icedream61 博客园(转载请注明出处)--------------------------------------------------------------- ...

  7. USACO Section1.5 Prime Palindromes 解题报告

    pprime解题报告 —— icedream61 博客园(转载请注明出处)--------------------------------------------------------------- ...

  8. USACO Section2.3 Controlling Companies 解题报告 【icedream61】

    concom解题报告------------------------------------------------------------------------------------------ ...

  9. USACO Section2.3 Money Systems 解题报告 【icedream61】

    money解题报告------------------------------------------------------------------------------------------- ...

随机推荐

  1. 本元鼠标自动点击器 v1.31 官方绿色版

    软件名称: 本元鼠标自动点击器软件语言: 简体中文授权方式: 免费软件运行环境: Win 32位/64位软件大小: 516KB图片预览: 软件简介:本元鼠标自动点击器是一款免费绿色版的鼠标自动点击器, ...

  2. 启动tomcat时报错:java.lang.OutOfMemoryError: PermGen space

    1.修改myeclipse.ini 在Myeclipse安装目录下G:\MyEclipse8.5\Genuitec\MyEclipse 8.5有一个myeclipse.ini配置文件,设置如下: -v ...

  3. sql server显示某一列中有重复值的行

    sql server查询一张表 ,显示某一列中有重复值的行,可以这样写: Select * From 表名 where 列名 in(Select 列名 From Table group by 列名 h ...

  4. AutoTile 自动拼接 番外篇(自动融合技术)

    http://pan.baidu.com/s/1dDQyfSl 密码:ttud 先睹为快吧. 之后 还差一个 智能替换 技术.

  5. bootstrap开始咯

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name ...

  6. eclipse中maven工程的创建javaweb项目

    第一步.new一个新的工程 ,选中maven project,点击next 第2步.不做选择,点击next 第3步.选择maven-archetype-webapp,点击next 第四步.填写项目名称 ...

  7. OpenCV使用边缘提取、腐蚀、轮廓进行车牌定位

    http://blog.csdn.net/superdont/article/details/24935383 OpenCV使用边缘提取.腐蚀.轮廓进行车牌定位 2014-05-03 21:38 67 ...

  8. hdu_2110_Crisis of HDU(母函数)

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=2110 题意:给你N个价值和数目,求方案数,很裸的母函数. #include<cstdio> ...

  9. lldpd-0.7.7代码解读(send_pdu部分)

    此文档是经过逆序推到出的,可能有错误之处,敬请指教,谢谢. 1)interfaces_update 更新一些接口信息 2)levent_iface_subscribe 该接口通过socket通信(非阻 ...

  10. Entity Framework技巧系列之二 - Tip 6 - 8

    提示6. 如何及何时使用贪婪加载 什么时候你需要使用贪婪加载? 通常在你的程序中你知道对查询到的实体将要进行怎样的操作. 例如,如果你查询一个订单以便为一个客户重新打印,你知道没有组成订单的项目即产品 ...