P3320【小挑战】丢失的珠子
时间限制 : 20000 MS 空间限制 : 2500 KB
问题描述
何老板有一盒珠子共
n
n
n颗,编号
1
1
1到
n
n
n。他一不小心将盒子打翻,所有珠子都散落在地。他一颗一颗地把珠子捡起来,每捡一颗就记录下当前这颗珠子的编号。捡完以后发现少了两颗,请你快速找出少了哪两颗珠子。
输入格式
第一行,一个整数
n
n
n
接下来一行,
n
−
2
n-2
n−2个空格间隔的整数,表示何老板捡起来的珠子的编号。
输出格式
一行,由小到大排列的两个整数,表示丢失的两颗珠子的编号。
样例输入 1
7
4 1 7 2 5
样例输出 1
3 6
样例输入 2
10
4 5 8 3 9 7 1 2
样例输出 2
6 10
提示
对于30%的数据,有n<=1,000
对于100%的数据,有n<=1,000,000
喜欢用 cin
读入的小盆友,在程序主函数开头加上 ios::sync_with_stdio(false);
可以让程序的读入效率大幅提高
题意概括
找出从 1 1 1到 n n n中缺少的两个数
问题分析
看到这个题目,首先想到的就是暴力了
暴力怎么暴?
首先一个循环,读入
n
−
2
n-2
n−2个数,再来一个循环,检查每颗珠子有没有被捡起
#include<bits/stdc++.h>
using namespace std;
int n,x;
bool flag[1000010];
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n-2;i++){
cin>>x;
flag[x]=1;
}
for(int i=1;i<=n;i++) if(!flag[i]) cout<<i<<" ";
return 0;
}
算一算时间复杂度:
O
(
n
−
2
)
+
O
(
n
)
O(n-2)+O(n)
O(n−2)+O(n)约等于
O
(
2
n
)
O(2n)
O(2n),也就是
O
(
2
,
000
,
000
)
O(2,000,000)
O(2,000,000),不会爆。
再来算空间:
1
,
000
,
000
B
=
1
,
000
K
B
1,000,000B=1,000KB
1,000,000B=1,000KB,题目限制
2500
K
B
2500KB
2500KB,(当时小T脑袋一热就交上去了),结果?
事实证明,处理其他操作依然是要占空间的,我们不得不想另一种办法。
如果题目卡着空间不让我们开数组,那我们就考虑……
数学问题!
如果我们建立一个
s
u
m
sum
sum变量记录所有珠子的和,每捡起一颗就减去它的编号,剩下的就是丢失的两颗编号之和了。
设丢失的编号为
a
,
b
a,b
a,b,即
a
+
b
=
s
u
m
−
o
t
h
e
r
(
被捡起的编号之和
)
a+b=sum-other(被捡起的编号之和)
a+b=sum−other(被捡起的编号之和)。
一个方程,两个未知数,毕达哥拉斯告诉你:这是不可能解出来的。
那我们就还需要一个方程,构成二元方程组。
那么,
剩下的那个方程哪里来呢?
假设一个变量
q
d
t
qdt
qdt,记录平方和,同样的,每捡起一颗,就减去它编号的平方和。
则得到:
a
2
a^2
a2
+
+
+
b
2
=
q
d
t
−
o
t
h
e
r
2
b^2=qdt-other^2
b2=qdt−other2。
则得到一个二元方程组,就能解出
a
,
b
a,b
a,b了。
示例代码
#include<bits/stdc++.h>
using namespace std;
long long n,x,sum,qdt;
int main() {
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);//快读
cin>>n;
sum=n*(n+1)/2,qdt=n*(n+1)*(2*n+1)/6;
for(int i=1; i<=n-2; i++) {
cin>>x;
sum-=x;
qdt-=x*x;
}
for(int i=1; i<=sum; i++)
if(i*i+(sum-i)*(sum-i)==qdt) {//解方程,枚举
cout<<i<<" "<<sum-i;
return 0;
}
return 0;
}
没有数组,just a Maths Question!
接下来为拓展延伸板块
…
接着我为了检查对错,写了个对拍。
即自己造样例自己测,生成样例时要与格式保持一致。
这里需要用到rand()
函数,包含在<stdlib.h>
头文件里,
生成随机时间种子需要用到time()
函数,不同的时间生成会产生不同的数,包含在<time.h>
头文件里
对拍需要一个正解代码,一个暴力代码,一个生成样例的程序
示例
//ZhengJie.cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,x,sum,poq;
signed main() {
freopen("Data.in","r",stdin);
freopen("ZhengJie.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
sum=n*(n+1)/2,poq=n*(n+1)*(2*n+1)/6;
for(int i=1; i<=n-2; i++) {
cin>>x;
sum-=x;
poq-=x*x;
}
for(int i=1; i<=sum; i++) if(i*i+(sum-i)*(sum-i)==poq) {
cout<<i<<" "<<sum-i;
return 0;
}
return 0;
}
//Baoli.cpp
#include<bits/stdc++.h>
using namespace std;
int n,x;
bool flag[1000010];
int main(){
freopen("Data.in","r",stdin);
freopen("Baoli.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n-2;i++){
cin>>x;
flag[x]=1;
}
for(int i=1;i<=n;i++) if(!flag[i]) cout<<i<<" ";
return 0;
}
//MakeData.cpp
#include<bits/stdc++.h>
using namespace std;
bool flag[1000010];
int main() {
freopen("Data.in","w",stdout);
srand(time(NULL));
int n=rand()%1000000+1;
printf("%d\n",n);
for(int i=1; i<=n-2; i++) {
int x=rand()%n+1;
while(1){
if(!flag[x]) break;
x=rand()%n+1;
}
flag[x]=1;
printf("%d ",x);
}
return 0;
}
//Data.in(展示1组)
74
12 40 31 8 69 26 51 2 45 57 20 60 17 29 14 64 22 7 47 35 70 62 65 1 10 63 41 37 52 3 55 30 11 28 23 5 15 58 49 43 34 36 59 50 6 74 32 54 4 27 66 18 25 53 44 46 38 73 13 61 9 56 39 68 42 48 67 72 71 21 19 24
生成样例后,要用freopen()
读入数据,在保存在一个.out
文件中,最后写一个.bat
程序。
程序里应该写什么?
@echo off
:loop
MakeData.exe
ZhengJie.exe
Baoli.exe
fc ZhengJie.out Baoli.out
if not errorlevel 1 goto loop
pause
:end
最后关闭,双击打开后就可以进行比较了。
你可以看到你暴力程序和正解程序的输出一不一致,从而帮助你调试代码。
这种方法被我们成为对拍。