【紫书第五章习题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
23行 查找并返回迭代器 it = find(f
#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中有项
设a中有 N
设a中有 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相同。
(说明:对输入的每一项,若
于是可以写出以下代码:
#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;
}