【多解】UVa 10763 交换学生 Foreign Exchange【附输入数据构造参考代码】

【紫书第五章习题5-4】
开始做这题的时候用的是直接把每一行数对扔进map里然后一行行找是否有调换了两个数的有序整数对,有一个没找到,整个交换都不能进行。结果当然是样例都过不了,因为样例里就出现了第一个数一致但第二个数不同的情况,第二次及以后出现第一个数相同的数对根本无法输入进map中。后来试了multimap,不过写着写着发现写了4重循环(不知道我是不是想太多,写得过于复杂了),便放弃继续深究了,因为循环能到4重基本没有不超时的,准备重新按照新方法写之前随便试了一下,这份用multimap的代码坚持了180 ms然后WA。
今天自己写了一种方法(参见法一),然后结合网上找的题解改进了2个版本,把代码都贴出来供参考学习。
先说题目大意,单次输入包含多组数据,首先每一组输入的第一行是一个整数n,且1≤n≤500 000,代表参加交换生项目的学生总数。接下来n行是每个学生的交换意愿,写成两个整数的形式。整数用空格隔开,分别代表学生所在的原始地区和想去的地区。如果这个交换能进行(输出YES),则要求对于每个想由A地前往B地的学生,必须同时有一个想由B地前往A地的学生参加本次项目,否则整个交换项目失败(输出NO)。n=0时结束输入。所有地点坐标用非负整数表示,且假定每个学生想去的地点与出发地一定不同。

【法一】(约 260 ms。经过数次提交发现,运行时间可能会不一样,也出现过240 ms、290 ms、370 ms。)
算法概述:
将每个有序整数对存入映射 f 中,f的值是的出现次数。输入的在 f 中不存在时,添加映射关系
f=0,存在时直接将对应的出现次数+1。对 cur 为当前正在输入或查找的有序整数对。
输入完毕后,对任意有序整数对,若均有 f == f,则每个学生都可以配对,交换项目得以进行。否则失败。特别地,查找不到 f(返回迭代器 f.end())时,也代表有学生无法配对,项目失败。
由于对每个有序实数对 f 需要1~2次从整个映射中查找:
23行 查找并返回迭代器 it = find(f)、若找到则判定 f == f(f的位置由迭代器 it 给出,不用再查找),耗时较长。

#include
#include
#include
#include
#include
#pragma warning(disable:4996)
using namespace std; typedef unsigned int uint;
map<pair<uint, uint>, uint> f; pair<uint, uint> cur;
int main() {
	uint n; bool verdict; map<pair<uint, uint>, uint>::iterator it;
	for (;;) {
		scanf("%u", &n); if (n == 0)break;
		f.clear();
		for (uint i = 0; i < n; ++i) {
			scanf("%u%u", &cur.first, &cur.second); 
			it = f.find(cur);
			if (it == f.end())f.emplace(cur, 0);
			else ++(*it).second;
		}
		verdict = true;
		for (map<pair<uint, uint>, uint>::iterator i = f.begin(); i != f.end(); ++i) {
			cur.first = (*i).first.second, cur.second = (*i).first.first;
			it = f.find(cur); if (it == f.end()|| f[(*i).first] != f[cur]) { verdict = false; puts("NO"); break; }
		}
		if (verdict == true)puts("YES");
	}
	return 0;
}

【法二】(90 ms)
原本是看了这篇文章:https://blog.csdn.net/shuangde800/article/details/7803942 提到的内容:

swap(arr[A], arr[B])。
全部处理完后, 判断这个序列是否还是和原来的排列一样。如果是的话,就符合条件。否则就不行。(因为对于每个swap(arr[A]),arr[B])如果有一个相对应的swap(arr[B], arr[A]), 那么相当于恢复了原排列)

实验发现,该文章内的代码在UVAOJ上执行并判对仅需40 ms。但是后来发现该篇博客的代码有错,且已经通过手动构造特殊输入发现代码给出了错误结果(OJ莫名判对,具体见原文评论区),不过思路可以借鉴。于是有了下面的算法。
算法概述:
将所在地区和目标地区构造为有序整数对 ,并建立有序整数对数组a、b。输入数据时,将原数据放入数组a,并将该次输入的数对交换后放入数组b(输入完毕或输入时再构建数组b均不影响结果)。见下图(图中应该把n改成m,不小心写错了,与题目中给的n混淆,不过懒得改了):
【多解】UVa 10763 交换学生 Foreign Exchange【附输入数据构造参考代码】_第1张图片
对任意一个输入,如果每个学生都能配对,即a中每个 都对应一个 两个坐标相同的项重复出现则认为是不同项) 。在这个条件下,既然b的项只是将a中对应项的 交换为 得到的,所以两个数组含有相同的项,只是顺序不同。将两个数组用sort排列(需要重定义<运算符或自定义比较函数。比较规则:按x从小到大排列,如果x相同,则按y从小到大排列),则会得到相同的数组。
【多解】UVa 10763 交换学生 Foreign Exchange【附输入数据构造参考代码】_第2张图片
如果不是每个学生都能配对,即a中有项 的出现次数 N 与项 的出现次数 N 不等。则分两种情况:
设a中有 N > N,则构造的数组b中会变为 N < N
设a中有 N > N,则构造的数组b中会变为 N < N
显然数组b与数组a不可能相同,示意图略,不理解可以自己在稿纸上仿照上面的图画出来。
于是,我们得出结论:
输入的每项 均能对应
→ 设输入数据保存到数组a,则仅通过交换a的每一项、将交换结果存入新数组并重排就可得到与a完全相同的数组b;
有输入项 不能对应
→ 设输入数据保存到数组a,则仅通过交换a的每一项、将交换结果存入新数组并重排不可得到与a完全相同的数组b;
根据“原命题与逆否命题同真”可知,判断如上方法构造的数组a、b是否相同,可以判断原输入a是否对其中任意一项 均可以对应一个 。(两个坐标相同的项重复出现则认为是不同项
便可以写出如下代码(AC):

#include
#include
#pragma warning(disable:4996)
using namespace std; typedef unsigned int uint;
struct LocationPair { uint x, y; };
bool operator < (const LocationPair& a, const LocationPair& b) {
	if (a.x != b.x)return a.x < b.x;
	return a.y < b.y;
}
LocationPair a[500000], b[500000];
int main() {
	uint n; bool verdict;
	for (;;) {
		scanf("%u", &n); if (n == 0)break;
		for (uint i = 0; i < n; ++i) {
			scanf("%u%u", &a[i].x, &a[i].y), b[i].x = a[i].y, b[i].y = a[i].x;
		}
		sort(a, a + n), sort(b, b + n); verdict = true;
		for (uint i = 0; i < n; ++i) {
			if (a[i].x != b[i].x || a[i].y != b[i].y) { verdict = false; puts("NO"); break; }
		}
		if (verdict == true)puts("YES");
	}
	return 0;
}

【法三】(60 ms)
我用法一AC以后,在网上搜代码对比执行时间,发现也有一份运行时间很短的代码:http://www.voidcn.com/article/p-szniykik-bdc.html
算法概述:
将输入的两个数分别放入数组a、b,输入完毕后排序。如果输入的每个有序整数对 都能对应 两个坐标相同的项重复出现则认为是不同项),即输入数据存在 ↔ 输入数据存在对应的 ,那么显然有
N(xi) == N(yi),N(x)为项x的出现次数。
又有:
Na(xi) == Nb(xi), Na(yi) == Nb(yi),Na(x)、Nb(x)分别为项x在数组a、数组b中出现的次数。
又即:数组a、b相同。
(说明:对输入的每一项,若∈f ↔ ∈f,则a、b分别得到:一个xi、一个yi。如果有项 不对应 ,则仅a得到一个xi,b得到一个yi。那么a的xi出现次数比b多一次, a的yi出现次数比b少一次。)
于是可以写出以下代码:

#include
#include
#pragma warning(disable:4996)
using namespace std; typedef unsigned int uint;
uint a[500000], b[500000];
int main() {
	uint n; bool verdict;
	for (;;) {
		scanf("%u", &n); if (n == 0)break;
		for (uint i = 0; i < n; ++i)
			scanf("%u%u", &a[i], &b[i]);
		sort(a, a + n), sort(b, b + n), verdict = true;
		for (uint i = 0; i < n; ++i)
			if (a[i] != b[i]) { verdict = false; puts("NO"); break; }
		if (verdict == true)puts("YES");
	}
	return 0;
}

相比原代码不同的是,声明数组的时候没有赋值0,因为输入数据后会写到数组里,无需初始化。
第三个方法更快的原因是:避免了struct(带我的学长说结构体多了会拖慢速度,当然这里的struct应该不会拖慢多少),减少了处理和写入的量(方法2需要把数组a的每一项先交换,后写入b,而且排序的操作量也更大)。
附输入数据构造参考代码(构造的数据已经提交到uDebug,每份输入512组,每组输入最多1024行,地点标号从0到32767,极小概率出现32768。构造结果保存在程序所在目录下的i.txt中):

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#pragma warning(disable:4996)
using namespace std; typedef unsigned short ushort; typedef unsigned char uchar; typedef unsigned int uint; typedef unsigned long long ull; typedef long long ll;
int main() {
	clock_t t0, t = clock(); srand(clock()); FILE* f = fopen("i.txt", "w"); uchar verdict, m, exitfor; vector<pair<uint, uint>> v; ushort n, n0, x, y, times, p1, p2;
	for (ushort i = 0; i < 512; ++i) {
		t0 = clock(), n = rand() % 1024 + 1;
		verdict = rand() % 2;//用于决定是否构造答案为YES的输入数据
		switch (verdict) {
		case 0:fprintf(f, "%hu\n", n);
			for (ushort j = 0; j < n; ++j) {
				fprintf(f, "%hu %hu\n", rand(), rand());
			}break;//随意生成数对,几乎不会产生YES的结果。
		default:v.clear(); if (n % 2 == 1)++n; //n必须是偶数,否则一定有学生无法配对,交换项目失败。
			fprintf(f, "%hu\n", n); exitfor = 0;
			for (ushort j = 0;; ) {
				m = rand() % 2 + 1;
				for (ushort k = 0; k < m; ++k) {
					x = rand(), y = rand();
					if (x == y)++y;//不能使原地区和目标地区相同。
					v.emplace_back(x, y), v.emplace_back(y, x);
					j += 2; if (j >= n) { exitfor = 1; break; }
				}//有几率构造重复的数对,数对出现次数为2次或1次。
				if (exitfor == 1)break;
			}//生成每个数对后,把交换次序的数对也输入进去,因为它们能配对。
			n0 = n / 2, times = rand() % n0 + 1;
			for (ushort j = 0; j < times; ++j) {
				p1 = rand() % n, p2 = rand() % n, swap(v[p1], v[p2]);
			}//打乱次序。
			for (ushort j = 0; j < n; ++j) {
				fprintf(f, "%hu %hu\n", v[j].first, v[j].second);
			}
		}//生成结果为YES的交换项目。
		printf("Group %hu construction completed. Duration = %d ms.\n", i, clock() - t0);
	}
	fputs("0\n", f);
	printf("Complete. Duration = %d ms.\n", clock() - t);
	system("pause"); return 0;
}

你可能感兴趣的:(ACM-ICPC)