为班内准备机考出的一套模拟题
#题目链接
#题意简述
若干个数中,有且仅有一个数字出现了一次,其余均出现两次。找到那个出现了一次的数。
#简要做法
观察到每个数均小于$1500$,我们可以用数组统计每个数字出现的次数。统计完后$O(n)$复杂度从小到大扫一遍,找到次数为一的数字,输出答案,退出循环。
#参考代码
|
int n; |
int a[1505]; |
int main() { |
scanf("%d" , &n); |
int t , mx = 0; |
for(int i = 1; i <= n; ++i) { |
scanf("%d" , &t); |
if(t > mx) mx = t;//找到最大的数字就够了 |
a[t]++; |
} |
for(int i = 1; i <= mx; ++i) |
if(a[i] == 1) { |
printf("%d\n" , i); |
break; |
} |
return 0; |
} |
想要更简单的做法?那我们首先需要学习异或的几条性质:
*异或是一种基于二进制的位运算,用符号XOR或者 ^ 表示,其运算法则是对运算符两侧数的每一个二进制位,同值取0,异值取1。它与布尔运算的区别在于,当运算符两侧均为1时,布尔运算的结果为1,异或运算的结果为0。*
按位异或便是将数字转为二进制后右对齐,每一位进行异或运算。位数不同时则位数少的前面补零。
由按位异或的性质,我们不难发现:
- 0 ^ x = x , x ^ x = 0
- a ^ b = b ^ a
- a ^ b ^ b = a ^ 0 = a
由此性质,本题出现两次的数字均可由异或运算化为0,从而直接得解。
#参考代码1
|
int n; |
int main() { |
scanf("%d",&n); |
int ans = 0 , t; |
for(int i = 1; i <= n; ++i) { |
scanf("%d" , &t); |
ans ^= t; //异或运算 |
} |
printf("%d\n" , ans); |
return 0; |
} |
#题目链接
B: [T113497 wxl的午餐
#简要做法
将吃饭时间(即$b_{i}$)按从大到小排序,求出最长的吃完时间。
该做法基于贪心的思想,下面给出证明:
我们不难发现,所有人吃完饭的最早时刻即为max{$b_i + \sum_{k=1}^ia_k$}。现在考虑相邻的两个人p、q。显然,是否调换p、q的顺序对于他们前面和后面的人的吃完饭时间均无影响。不妨设$b_p > b_q$,则有:
p在q前:
$time_p = () + a_p + b_p$
$time_q = () + a_p + a_q + b_q$
q在p前:
$time_{q1} = () + a_q + b_q$
$time_{p1} = () + a_q + a_p + b_p$
四项中的最大值显然出现在“q在p前”,因此要尽可能将b值大的往前放。
#参考代码
//std provided by yyn |
|
|
int a[1005],b[1005],n; |
int main() |
{ |
int n; |
scanf("%d",&n); |
int i,ii; |
for(i=1;i<=n;i++) |
{ |
scanf("%d%d",&a[i],&b[i]); |
} |
int ans=0,tmp=0; |
for(i=1;i<=n;i++) |
{ |
int t=1; |
for(ii=2;ii<=n;ii++) |
{ |
if(b[ii]>b[t])t=ii; |
} |
tmp+=a[t]; |
if(tmp+b[t]>ans)ans=tmp+b[t]; |
b[t]=-1; |
} |
printf("%d\n",ans); |
return 0; |
} |
UPD: 今天脑袋抽了,抄了个错误方法,大家忘掉就好。。。这个错误方法正确主要是我数据出水了(
求解正确答案是离不开排序的
#题目链接
C:[T112898 wxl的括号编辑器
#简要做法
基本就是模拟,题目说什么我们就做什么。记录好当前光标的位置,然后对相应位置进行修改即可。修改答案字符串的同时,在相同位置打好标记,以便于后续判断括号序列是否合法。
#参考代码
|
|
char ans[1005] , s[1005]; |
int a[1005] , n; |
int main() { |
scanf("%d%s" , &n , s); |
int pos = 0; |
for(int i = 0; i < n; ++i) { |
if(s[i] == 'L' && pos - 1 >= 0) pos--; |
else if(s[i] == 'R') pos++; |
else if(s[i] == '(') { |
ans[pos] = '('; |
a[pos] = 1; |
} |
else if(s[i] == ')') { |
ans[pos] = ')'; |
a[pos] = -1; |
} |
} |
printf("%s\n" , ans); |
int l = strlen(ans); |
int sum = 0; |
for(int i = 0; i < l; ++i) { |
sum += a[i]; |
if(sum < 0) { |
printf("No\n"); |
return 0; |
} |
} |
if(sum == 0) |
printf("Yes\n"); |
else printf("No\n"); |
return 0; |
} |
#题目链接
#题意简述
找到最小的整数x,使得$x^2/a + \sqrt{x} >= n$。
#简要做法
首先注意到$n,a$均很大,因此答案可能会很大,需要用long long进行运算,否则会爆int(涉及平方运算时)。
50PTS做法:
从小到大枚举,找到第一个满足条件的值,将其输出。时间复杂度$O(tn)$
#参考代码
|
|
|
ll t , n , a; |
int main() { |
scanf("%lld" , &t); |
while(t--) { |
scanf("%lld%lld" , &n , &a); |
ll v; |
for(ll i = 0; i <= n; ++i) { |
v = i * i / a + sqrt(i); |
if(v >= n) { |
printf("%lld\n" , i); |
break; |
} |
} |
} |
return 0; |
} |
100PTS做法:
线性复杂度太慢,观察到趋势是单调的,我们可以用二分查找这个答案。时间复杂度$O(t\log_2n)$。
#参考代码
|
|
int t; |
long long n , a; |
int check(long long x) { |
long long tmp = sqrt(x) + x * x / a; |
return tmp >= n; |
} |
int main() { |
scanf("%d" , &t); |
while(t--) { |
scanf("%lld%lld" , &n , &a); |
long long l = 0 , r = n , ans; |
while(l <= r) { |
//long long mid = l + r >> 1; |
long long mid = (l + r) / 2; |
if(check(mid)) { |
ans = mid; |
r = mid - 1; |
} |
else l = mid + 1; |
} |
printf("%lld\n" , ans); |
} |
return 0; |
} |
#题目链接
#简要做法
首先注意到计算得到的数字可能会超出int范围,因此用long long储存变量数值。
70PTS:
对于每一次询问,用循环计算出答案,时间复杂度$O(mn)$。
#参考代码
|
|
const ll mod = 1e9 + 7; |
ll a[30005]; |
int n , m; |
int main() { |
scanf("%d%d" , &n , &m); |
for(int i = 1; i <= n; ++i) |
scanf("%lld" , &a[i]); |
ll l , r; |
for(int i = 1; i <= m; ++i) { |
scanf("%lld%lld" , &l , &r); |
ll sum = 0; |
for(int j = l; j <= r; ++j) { |
sum = (sum + (j - l + 1) * a[j]) % mod; |
} |
printf("%lld\n" , sum); |
} |
return 0; |
} |
100PTS做法:
我们想到对于每一次询问,我们都要对左右区间内的元素进行一次运算,但是这样想必是很浪费时间的。我们能否想到一个方法,使得将这个过程优化到$O(1)$的复杂度呢?
要实现这样的目的,我们首先介绍前缀和的概念:
我们定义前缀和$s_i=\sum_{k=1}^ia_k$,这样,想求$\sum_{i=l}^ra_i$,便可直接用$s_r-s_{l-1}$得到,时间复杂度为$O(1)$。
//前缀和实现 |
s[0] = 0; |
for(int i = 1; i <= n; ++i) |
s[i] = s[i - 1] + a[i]; |
介绍完前缀和,我们如何达成本题的目的呢?
展开
$\sum_{i=l}^ra_i \cdot (i - l + 1)$为
$a[l]\cdot1 + a[l+1]\cdot2+……+a[r-1]\cdot(r-l)+a[r]\cdot(r-l+1)$
$=(s[l]-s[l-1])+..+(s[r-1]-s[r-2])\cdot(r-l)+(s[r]-s[r-1])\cdot(r-l+1)$
$=-(s[l-1]+s[l]+s[l+1]+…..+s[r-1])+(r-l+1) \cdot s[r]$
到这一步我们发现我们可以对s数组进行求和,于是再计算s的前缀和e
所求$=(r-l+1) \cdot s[r]-(e[r-1]-e[l-2])$
#参考代码
|
|
const int mod = 1e9 + 7; |
int n , m; |
ll a[30005] , s[30005] , e[30005]; |
int main() { |
scanf("%d%d" , &n , &m); |
for(int i = 1; i <= n; ++i) { |
scanf("%lld" , &a[i]); |
s[i] = s[i - 1] + a[i];//a的前缀和 |
e[i] = e[i - 1] + s[i];//s的前缀和 |
} |
ll l , r; |
for(int i = 1; i <= m; ++i) { |
scanf("%lld%lld" , &l , &r); |
ll ans = (((r - l + 1) * s[r]) % mod - (e[r - 1] - e[l - 2]) % mod + mod) % mod;//由于都进行了取模,减法可能会减出负数,因此要加上1个mod再取模 |
printf("%lld\n" , ans); |
} |
return 0; |
} |