这道题挺简单的,就是给好几个数列,每个数列里面有可能有重复元素,再给定几个查询,每个查询给了两个数列编号(从1开始),问在这两个数列中都出现的数的个数nc 占 两个数组中的去掉重复数据后的数的个数nt的百分比。
比如说
99 87 101
87 101 5 87
这两个数组中,重复出现的元素为87,101,故nc = 2。而所有数据为5、87、99、101,即nt = 4,所以占比为50.0%。
因为原数列中就可能包含有重复元素,所以使用unordered_set < int >来存储所有数列,这样原数组中的重复元素便只保留一个。
之后对于每次查询,使用unorder_map < int,bool > vis来记录数据是否出现过,并使用unordered_set < int > total,common来记录总的数据以及共同的数据。首先遍历第一个数列,在遍历过程中,将第一个数列中所有出现的数据全部置为已经出现过,并且将数据加入到total中。再之后,遍历第二个数组,首先将第二个数组中出现的数据加入到total中,如果当前数据已经在第一个数组中出现过了,则加入到common中去。这是最直观的想法,最朴素的想法,也就是暴力法
代码如下
#include <bits/stdc++.h>
using namespace std;
unordered_set<int> st[51];
int main()
{
int n;
scanf("%d",&n);
for(int cur = 1;cur <= n;cur++){
int cnt;
scanf("%d",&cnt);
for(int i = 0;i < cnt;i++){
int t;
scanf("%d",&t);
st[cur].insert(t);
}
}
int k;
scanf("%d",&k);
for(int r = 0;r < k;r++){
int id1,id2;
scanf("%d%d",&id1,&id2);
unordered_map<int,bool> vis;
unordered_set<int> total,common;
for(auto it = st[id1].begin();it != st[id1].end();it++){
vis[*it] = true;
total.insert(*it);
}
for(auto it = st[id2].begin();it != st[id2].end();it++){
if(vis[*it]) common.insert(*it);
total.insert(*it);
}
printf("%.1f%%\n",100.0 * common.size() / total.size());
}
return 0;
}
但是非常不幸,这样的话,第四个测试点是超时的。就是因为,数据量太大了,这种思路是没有错的,但是仔细分析以下,第一次遍历时,就需要O(st[id1].size())大小的复杂度,更不用说在遍历时还要往total中insert数据,以及往vis中建立映射,这两个都是非常耗费时间的。将vis[*it] 变为true,虽然只是整型,但是数据量太大时,也是耗费时间的。
第二次遍历,复杂度为O(st[id2].size())大小,但是还包含往total中添加数据,判断vis[*it]是否为true,以及如果是true时,还要往common中insert数据,都是非常耗费时间的。
思路没有问题,但是数据量太大时,耗时多,就得换个思路。
最好的思路是,遍历第一个数组,然后对于当前的数据 x 去第二个数组中去查找是否存在,如果找得到,那么就是在两个数组中都出现过的数据。
这样的话,遍历第一个数组用复杂度O(st[id1].size()),再加上去第二个数组中去查找,无论是使用count还是find都是O(log(st[id2].size()))的复杂度,比上个思路优秀很多,并且不用额外添加数据、建立映射。
代码如下
#include <bits/stdc++.h>
using namespace std;
unordered_set<int> st[51];
int main()
{
int n;
scanf("%d",&n);
for(int cur = 1;cur <= n;cur++){
int cnt;
scanf("%d",&cnt);
for(int i = 0;i < cnt;i++){
int t;
scanf("%d",&t);
st[cur].insert(t);
}
}
int k;
scanf("%d",&k);
for(int r = 0;r < k;r++){
int id1,id2;
scanf("%d%d",&id1,&id2);
int nc = 0;
for(auto it = st[id1].begin();it != st[id1].end();it++){
if(st[id2].find(*it) != st[id2].end()) nc++;//使用find,去第二个数列中
} //查找该数出现过没有
printf("%.1f%%\n",100.0 * nc / (st[id1].size() + st[id2].size() - nc));
}
return 0;
}
总结
不要直接想暴力,多思考。
set的find以及count的复杂度都是O(logn)的,要比遍历优秀太多。
C++11中可以使用以下方法来遍历set
#include <bits/stdc++.h>
using namespace std;
int main()
{
set<int> st;
for(int i = 0;i < 10;i++) st.insert(i);
for(auto x : st) printf("%d ",x);//x是数据
}
或者
#include <bits/stdc++.h>
using namespace std;
int main()
{
set<int> st;
for(int i = 0;i < 10;i++) st.insert(i);
for(auto it = st.begin();it != st.end();it++)//it是迭代器
printf("%d ",*it);
}