题目
“单身狗”是中文对于单身人士的一种爱称。本题请你从上万人的大型派对中找出落单的客人,以便给予特殊关爱。
输入格式:
输入第一行给出一个正整数 N(≤ 50 000),是已知夫妻/伴侣的对数;随后 N 行,每行给出一对夫妻/伴侣——为方便起见,每人对应一个 ID 号,为 5 位数字(从 00000 到 99999),ID 间以空格分隔;之后给出一个正整数 M(≤ 10 000),为参加派对的总人数;随后一行给出这 M 位客人的 ID,以空格分隔。题目保证无人重婚或脚踩两条船。
输出格式:
首先第一行输出落单客人的总人数;随后第二行按 ID 递增顺序列出落单的客人。ID 间用 1 个空格分隔,行的首尾不得有多余空格。
输入样例:
3
11111 22222
33333 44444
55555 66666
7
55555 44444 10000 88888 22222 11111 23333
输出样例:
5
10000 23333 44444 55555 88888
分析
将每个ID值都看作int型,对于从 00000 到 99999的ID则对应的int为0-99999
一.创建字典 记录couple值
①创建一个dic[100000]的数组,初始化为-1(默认无couple)
②获取每一组couple的值,记录在数组中,以样例的第一组couple为例:
dic[11111]=22222,dic[22222]=11111,如此记录。
二.创建guset[100000] 标记来宾ID
①创建一个guest[100000]的数组,初始化为0(默认ID对应的人没参加)
②获取参加派对人的ID,记录到guest数组中,即guest[i]++表示ID为i的人参加了派对
三.查询字典,将单身狗加入结果集合ans中
1.遍历已经构造完成的guest数组,对于到来的客人(即guset[i]==1)分为以下两种情况
①dic[i]<0 ,此时来宾i无couple
②guest[dic[i]]==0 ,此时来宾i有couple,但其couple没参加
2.将满足上述情况之一的便加入集合ans中
四.遍历集合ans输出结果
! 这里要注意用dic[i]<0 || guest[dic[i]] == 0将情况1写在前面并用 逻辑或 || ,因为逻辑或在运算中有短路特性,当 dic[i] < 0 时会结束整个运算, 排除了guest[-1]的下标越界的情况
代码
1.0版本
第一次AC的代码,最开始没考虑输出的不是五位数的情况,由于将字符串转化成了对应了整型,会导致左端缺0,没通过测试点3,用printf的格式化输出即可。
printf("%05d",*it);
#include<iostream>
#include<cmath>
#include<string>
#include<set>
using namespace std;
int TransToInt(string s){
int sum=0;
for(int i=0;i<s.length();i++){
sum+=(s[i]-'0')*pow(10,4-i);
}
return sum;
}
int main(){
int N,M,a,b,dic[100000];
for(int i=0;i<100000;i++) dic[i]=-1;
set<int> s,ans;
string s1,s2;
cin>>N;
for(int i=0;i<N;i++){
cin>>s1>>s2;
a=TransToInt(s1);b=TransToInt(s2);
dic[a]=b;dic[b]=a;
}
cin>>M;
for(int i=0;i<M;i++){
cin>>s1;
s.insert(TransToInt(s1));
}
for(auto it=s.begin();it!=s.end();it++){
if(s.find(dic[*it])==s.end()) ans.insert(*it);
}
printf("%d\n",ans.size());
for(auto it=ans.begin();it!=ans.end();it++){
if(it!=ans.begin()) cout<<" ";
printf("%05d",*it);
}
return 0;
}
2.0版本
看了柳神的代码后的改进,其实最开始我也想到了多用一个数组存放guest的信息,但是没想好怎么之后处理,所以用了两个set。
与柳神思路略有不同,我这里多加了一个guest数组用于存放guest的信息,初始化为0,若对应位置的客人到来则变为1。之后对guest数组进行遍历,若guest[i]==0则跳过,对于值为1的位置,去dic[i]中寻找其对应的couple值,再根据guest[dic[i]]查询是否其couple也是来宾,若其couple没来则表示其落单,加入ans集合中。
#include<iostream>
#include<set>
#include<vector>
using namespace std;
int main(){
int N,M,a,b;
vector<int> dic(100000, -1),guest(100000,0);
set<int> ans;
scanf("%d", &N);
for(int i = 0;i < N;i++){
scanf("%d%d",&a,&b);
dic[a]=b;dic[b]=a;
}
scanf("%d", &M);
for(int i = 0;i < M;i++){
scanf("%d",&a);
guest[a]++;
}
for(int i = 0;i < 100000;i++){
if(guest[i] == 0) continue;
if(guest[dic[i]] == 0) ans.insert(i);
}
printf("%d\n",ans.size());
for(auto it = ans.begin();it != ans.end();it++){
if(it != ans.begin()) cout<<" ";
printf("%05d",*it);
}
return 0;
}
3.0版本
排除下标存在越界可能后的最终版本
#include<iostream>
#include<set>
#include<vector>
using namespace std;
int main(){
int N,M,a,b;
vector<int> dic(100000, -1),guest(100000,0);
set<int> ans;
scanf("%d", &N);
for(int i = 0;i < N;i++){
scanf("%d%d",&a,&b);
dic[a]=b;dic[b]=a;
}
scanf("%d", &M);
for(int i = 0;i < M;i++){
scanf("%d",&a);
guest[a]++;
}
for(int i = 0;i < 100000;i++){
if(guest[i] == 0) continue;
if(dic[i]<0 || guest[dic[i]] == 0) ans.insert(i);
}
printf("%d\n",ans.size());
for(auto it = ans.begin();it != ans.end();it++){
if(it != ans.begin()) cout<<" ";
printf("%05d",*it);
}
return 0;
}
总结
文章写着写着发现了一个问题,由于开始把couple字典的默认值设置为了-1,当guest[i] != 0(即i为来宾),对来宾i在字典中查询i的couple值时,若来宾i无coupl则返回的是-1,然后在guest中查询来宾i的couple是否到来是,实际上是通过guest[-1]的值来判断,此时下标为-1,很明显下标越界了,应该先判断来宾i存在couple的前提下(即 dic[i] != -1),再判断来宾中是否存在来宾i的couple(guest[dic[i]] == 0)
但是代码2.0又通过了所有测试…这就产生了一个问题,PAT内部的测试程序应该把vector越界的内容设置为了0,在自己电脑上测试时,代码2.0结果异常
在代码3.0中把 guest[dic[i]] == 0 改为了 dic[i]<0 || guest[dic[i]] == 0这里用了**||** 运用其短路计算的特性,在 dic[i]<0成立时便结束整个运算,排除了下标为-1的情况