[JLOI2011]不重复数字

本文介绍了解决JLOI2011不重复数字问题的方法,利用C++ STL中的set去除重复数字,并提供了一种使用自平衡二叉搜索树(SBT)的解决方案。

2761: [JLOI2011]不重复数字

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 2459  Solved: 939
[Submit][Status][Discuss]

Description

给出N个数,要求把其中重复的去掉,只保留第一次出现的数。
例如,给出的数为1 2 18 3 3 19 2 3 6 5 4,其中2和3有重复,去除后的结果为1 2 18 3 19 6 5 4。
 

Input

输入第一行为正整数T,表示有T组数据。
接下来每组数据包括两行,第一行为正整数N,表示有N个数。第二行为要去重的N个正整数。
 

Output

 
对于每组数据,输出一行,为去重后剩下的数字,数字之间用一个空格隔开。

Sample Input

2
11
1 2 18 3 3 19 2 3 6 5 4
6
1 2 3 4 5 6

Sample Output

1 2 18 3 19 6 5 4
1 2 3 4 5 6

HINT

 

对于30%的数据,1 <= N <= 100,给出的数不大于100,均为非负整数;


对于50%的数据,1 <= N <= 10000,给出的数不大于10000,均为非负整数;


对于100%的数据,1 <= N <= 50000,给出的数在32位有符号整数范围内。


提示:


由于数据量很大,使用C++的同学请使用scanf和printf来进行输入输出操作,以免浪费不必要的时间。


 

 

Source

                   [Submit][Status][Discuss]
 
  本来是个平衡树,但是可以用STL中的set来作,具体set有讲解:
  http://blog.csdn.net/lyhvoyage/article/details/22989659
  今天第一次见识了PE,PE与AC的区别是格式的问题,如多了一个空格什么的。。。。
 1 #include<bits/stdc++.h> 
 2 using namespace std;
 3 set<int>S;
 4 int T,n,x,a[50001],b[50001],end;
 5 int main()
 6 {    
 7     scanf("%d",&T);
 8     for(;T;T--) {    
 9         scanf("%d",&n); 
10         S.clear(); end=0;    
11         for(int i=1;i<=n;i++) 
12             scanf("%d",&a[i]);
13         for(int i=1;i<=n;i++)
14             if(S.find(a[i])==S.end()){//不存在该元素 
15                 S.insert(a[i]);
16                 b[++end]=a[i];
17             }
18         for(int i=1;i<end;i++) 
19             printf("%d",b[i]),cout<<" ";
20         cout<<b[end]<<endl;
21     }
22     return 0; 
23 }

   再来一发SBT的(PE)。

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstdlib>
  4 #include<algorithm>
  5 #include<cmath>
  6 #include<cstring>
  7 #include<queue>
  8 #include<vector>
  9 using namespace std;
 10 typedef long long LL; 
 11 const LL maxn=500100;
 12 LL key[maxn],siz[maxn],lc[maxn],rc[maxn];
 13 LL root,tot;
 14 LL T,N;
 15 void r_rotate(LL &rt){
 16     LL k=lc[rt];
 17     lc[rt]=rc[k];
 18     rc[k]=rt;
 19     siz[k]=siz[rt];
 20     siz[rt]=siz[lc[rt]]+siz[rc[rt]]+1;
 21     rt=k;
 22 }
 23 void l_rotate(LL &rt){
 24     LL k=rc[rt];
 25     rc[rt]=lc[k];
 26     lc[k]=rt;
 27     siz[k]=siz[rt];
 28     siz[rt]=siz[lc[rt]]+siz[rc[rt]]+1;
 29     rt=k;
 30 }
 31 void Maintain(LL &rt,bool flag){
 32     if(flag==false){
 33         if(siz[lc[lc[rt]]]>siz[rc[rt]]) r_rotate(rt);
 34         else if(siz[rc[lc[rt]]]>siz[rc[rt]]){
 35             l_rotate(lc[rt]);
 36             r_rotate(rt);
 37         }
 38         else return ;
 39     }
 40     else{
 41         if(siz[rc[rc[rt]]]>siz[lc[rt]]) l_rotate(rt);
 42         else if(siz[lc[rc[rt]]]>siz[lc[rt]]){
 43             r_rotate(rc[rt]);
 44             l_rotate(rt);
 45         }
 46         else return ;
 47     }
 48     Maintain(lc[rt],false); Maintain(rc[rt],true);
 49     Maintain(rt,false); Maintain(rt,true);
 50 }
 51 void insert(LL &rt,LL v){
 52     if(rt==0){
 53         rt=++tot;
 54         key[rt]=v;
 55         siz[rt]=1; lc[rt]=rc[rt]=0;
 56         return ;
 57     }
 58     siz[rt]++;
 59     if(v<=key[rt]) insert(lc[rt],v);
 60     else insert(rc[rt],v);
 61     Maintain(rt,false); Maintain(rt,true);
 62 }
 63 LL Delete(LL &rt,LL v){
 64     LL ans;
 65     siz[rt]--;
 66     if(v==key[rt]||(v<key[rt]&&lc[rt]==0)||(v>key[rt]&&rc[rt]==0)){
 67         ans=key[rt];
 68         if(lc[rt]==0||rc[rt]==0) rt=lc[rt]+rc[rt];
 69         else key[rt]=Delete(lc[rt],key[rt]+1);
 70         return ans;
 71     }
 72     if(v<key[rt]) ans=Delete(lc[rt],v);
 73     else ans=Delete(rc[rt],v);
 74     return ans;
 75 }
 76 bool find(LL &rt,LL v){
 77     if(rt==0) return false;
 78     if(v==key[rt]) return true;
 79     if(v<key[rt]) return find(lc[rt],v);
 80     if(v>key[rt]) return find(rc[rt],v);
 81 }
 82 int main(){
 83     scanf("%lld",&T);
 84     while(T--){
 85         memset(siz,0,sizeof(siz)); memset(key,0,sizeof(key));
 86         memset(lc,0,sizeof(lc)); memset(rc,0,sizeof(rc));
 87         tot=root=0;
 88         scanf("%lld",&N);
 89         for(LL i=1,tmp;i<=N-1;i++){
 90             scanf("%lld",&tmp);
 91             if(find(root,tmp)==true) continue;
 92             else{
 93                 insert(root,tmp);
 94                 printf("%lld ",tmp);
 95             }
 96         }
 97         printf("\n");
 98     }
 99     return 0;
100 }

 

转载于:https://www.cnblogs.com/CXCXCXC/p/4712182.html

<think>好的,我现在得解决这个问题。题目是说,对于给定的n个数,要去掉重复的,只保留第一次出现的那个。而且输入有多组数据。那怎么实现呢? 首先,我得理解题意。比如样例输入中的第一个例子,数字3出现了多次,在输出中只保留第一次出现的那个。所以顺序很重要,必须保持第一次出现的顺序,后面的重复元素都要被去除。 那怎么处理呢?我的思路是遍历每个数字,记录哪些数字已经出现过。没有出现过的就保留下来,并标记为已出现。否则就跳过。这样就能得到去重后的序列,同时保持顺序。 那具体怎么实现呢?因为需要快速查询是否已经存在,所以应该用哈希表或者集合这样的数据结构。比如,每次读取一个数,检查是否在集合里。如果在,就添加到结果数组里,并加入集合。如果在的话就跳过。 那具体到C++的话,可以用unordered_set来存储已经出现过的数字。因为unordered_set的查找时间是O(1)的,这样效率比较高。因为n可能达到5e4,所以效率很重要。 然后处理多组数据的问题。对于每组测试数据,都要重新初始化集合和结果数组。输入n之后,接着输入n个数。然后逐个处理这些数,按上述方法去重。最后输出处理后的结果。 那关于输入输出的处理需要注意什么呢?比如,输入的时候可能会有多个空格分隔的数字。可以用cin来读取每个数,或者用istringstream来分割字符串。过对于较大的数据量来说,cin可能比较慢。但题目中的n最多是5e4,每组的话应该没问题。 比如,对于每组数据,我们读取n之后,再读取n个数。可以使用循环读取的方式,或者一次性读取整个行然后分割。比如,在C++中,有时候使用cin.getline()或者getline(cin, s)来读取一行,然后用stringstream来分割字符串。 但是需要注意,当cin读取完n之后,可能会有换行符留在输入流中,这时候直接用getline可能会出现问题。所以可能需要先处理这个问题。或者,如果输入的每个数的读取方式正确的话,可能会有问题。 比如,对于每组数据的第二行,可能用以下方式读取: vector<int> nums(n); for(int i=0; i <n; ++i){ cin >> nums[i]; } 这样可能更直接,但要注意在读取完前一个数据后是否有换行的问题。或者,当输入的数据很大时,可以考虑更快的输入方式,比如用scanf或者关闭同步流。 但这里的问题可能需要考虑效率特别高的输入,因为题目给出的时间限制可能允许常规的做法。过对于unordered_set来说,插入和查找的效率应该足够高。 那代码的大体结构应该是: 读取T组数据: for(T--){ 读取n; 读取n个数; 处理这n个数,去重; 输出结果; } 那么如何实现处理去重? 初始化一个空的unordered_set<long long>(因为题目中的数是32位有符号整数,可能int够,但C++中的int通常是32位,可能long long更安全?或者其实数值范围没问题,只要变量类型正确。或者用int即可。) 然后遍历每个数,判断是否存在于集合中。如果存在,就加入集合,并保留该数。否则跳过。 结果序列的顺序应该是保留第一个出现的顺序。例如,原顺序中的第一个数一定在结果中,后面的相同元素被跳过。所以遍历的时候要按顺序保留。 这样,可以用一个vector来保存结果顺序。最后输出这个vector的元素顺序。 比如: vector<int> res; unordered_set<int> seen; for(int num : nums){ if(seen.find(num) == seen.end()){ seen.insert(num); res.push_back(num); } } 然后输出res中的元素,每个元素之间用空格分隔。 输出的时候需要注意末尾是否有空格。例如,最后一个元素后面应该有空格。可以用循环,每个元素之间输出空格,或者用join的方式。 在C++中,可以用ostringstream来构建字符串,或者直接循环输出。 例如: for(int i=0; i < res.size(); ++i){ if(i ==0) cout << res[i]; else cout << " " << res[i]; } cout << endl; 或者,在循环中,先输出第一个元素,然后后面的每个元素前面加一个空格。这样可以保证元素之间只有一个空格。 但如何高效处理?比如,当结果中有大量元素时,使用字符串拼接可能会更高效。过对于每个测试用例来说,输出的元素最多是5e4个,这在C++中用循环输出每个元素还是可以接受的。 所以整体的代码结构大致如此。 那现在需要考虑边界条件吗? 比如,当n=0的情况?但题目中说明n>=1,所以需要。因为输入描述中的n是“一个整数n”,而约束是1<=n<=5e4。 哦,原题中的输入格式部分描述:输入的n的取值范围是1<=n<=5e4。所以无需处理n=0的情况。 那测试样例是否正确? 比如样例输入中的第一个例子: 输入是11个数,输出去重后的结果是8个数。原输入序列中的第二个3会被跳过,后面的2、3等都会被跳过。这样处理后,结果序列的顺序是正确的。 那这个方法是可行的。 那现在,编写代码需要注意的地方: - 多组数据的处理是否正确? - 每组数据的处理是否独立? - 输入的读取是否正确? - 输出的格式是否正确? 比如,在每组数据中,都需要重新初始化seen集合和res数组。否则,之前的数据会影响当前的处理。 比如,在循环外声明的变量,是否在每组数据开始的时候被清空? 例如,在循环内部声明seen和res吗? 比如: for(T--){ int n; cin >>n; vector<int> nums(n); for(...) read nums; // process unordered_set<int> seen; vector<int> res; for(...){ if not in seen... } // output res } 这样每组数据都会新建seen和res,所以没问题。 那这样结构是对的。 那现在,关于输入的读取。假设每组数据的第二行是n个数,可以用循环读入: for(int i=0; i <n; ++i){ cin >> nums[i]; } 或者,使用更高效的方式?或者可能输入的数据可能存在多个空格或者换行? 比如,当输入的n个数被包含在同一行时,上述方法是正确的。比如,使用循环读取每个数。 但是,当输入的每个数可能很大,或者有很多组数据时,使用cin的速度可能较慢。但题目中的T最多是50,每组n最多是5e4,所以总共有最多2.5e6次输入操作。这在C++中使用cin >> 应该还是可以接受的。但有时候,为了加速输入,可以关闭同步流: ios::sync_with_stdio(false); cin.tie(0); 这样可以加快输入速度。这对于处理大数据量是必要的。否则可能会超时。 所以代码的开头应该加上这个优化。 那综上,整个代码的框架应该是: #include <bits/stdc++.h> using namespace std; int main(){ ios::sync_with_stdio(false); cin.tie(0); int T; cin >> T; while(T--){ int n; cin >>n; vector<int> nums(n); for(int i=0; i <n; ++i){ cin >> nums[i]; } unordered_set<int> seen; vector<int> res; for(int num : nums){ if(seen.find(num) == seen.end()){ seen.insert(num); res.push_back(num); } } // output for(int i=0; i < res.size(); ++i){ if(i ==0) cout << res[i]; else cout << " " << res[i]; } cout << endl; } return 0; } 这样是否正确? 测试样例输入的情况: 比如样例输入中的第一个测试用例: 输入的n=11,数列是1 2 18 3 3 19 2 3 6 5 4。处理时,每个数依次检查: 1在,加入res和seen。 2在,同样处理。 18同理。 3同理。 第二个3在seen中,跳过。 19在,加入。 2在seen中,跳过。 3在seen中,跳过。 6、5、4都加入。 结果是1 2 18 3 19 6 5 4,共8个元素,与样例输出一致。 第二个测试用例所有元素都重复,输出原样。 所以代码应该能正确通过样例。 那可能存在的问题? 有没有可能数值超出int的范围?题目中说给出的数是32位有符号整数,所以int在C++中是32位的,所以没问题。所以用int即可。 那这样代码应该没问题。 那现在,根据用户的问题,我需要回答这个问题,并给出相关问题。 用户的问题是关于P4305题的C++实现。我的回答应该给出代码,并按照要求添加相关问题。 另外,注意输出格式中的每个数之间用空格隔开,行末没有空格。上面的输出循环方式是否正确? 比如,用i作为索引,第一个元素前加空格,后面每个元素前面加空格。这样每个元素之间用空格隔开。例如,输出形式是正确的。 比如,当res.size()为0时,会进入循环?但题目中n>=1,所以res至少有一个元素。因此,循环至少执行一次。 所以输出部分会有问题。 综上,代码是正确的。</think>```cpp #include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(0); int T; cin >> T; while (T--) { int n; cin >> n; vector<int> nums(n); for (int i = 0; i < n; ++i) { cin >> nums[i]; } unordered_set<int> seen; vector<int> res; for (int num : nums) { if (seen.find(num) == seen.end()) { seen.insert(num); res.push_back(num); } } for (int i = 0; i < res.size(); ++i) { if (i == 0) cout << res[i]; else cout << " " << res[i]; } cout << endl; } return 0; } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值