第一题:相似数组
在线测评链接:http://121.196.235.151/p/P1158
题目描述
ak机定义两个数组是相似的,当且仅当两个数组的总和相等。
ak机有两个长度为nnn的数组aaa和bbb,ak机想知道有多少个i(1≤i≤n)i(1≤i≤n)i(1≤i≤n)满足将aia_iai翻倍一次后,aaa数组和bbb数组是相似的。
输入描述
第一行输入一个整数 n(1<n≤105)n(1<n≤10^5)n(1<n≤105)表示数组长度
第二行输入nnn个整數表示数组a(−109≤ai≤109)a(-10^9≤ a_i≤ 10^9)a(−109≤ai≤109)
第三行输入nnn个整数表示数组b(−109≤bi<109)b(-10^9≤ b_i< 10^9)b(−109≤bi<109)
输出描述
输出可以得到的相似数组的个数
样例
输入
3
1 1 1
1 2 1
输出
3
思路:模拟
首先,分别统计数组a,ba,ba,b的元素总和,记为sum1,sum2sum1,sum2sum1,sum2,然后我们依次枚举将aaa数组的第iii个元素翻倍,是否可以使得其总和相等。
注意,本题数据范围较大,C++和Java选手需要开long long
C++
#include<bits/stdc++.h>
using namespace std;
const int N=1E5+10;
int n,a[N],b[N];
int main(){
cin>>n;
long long sum1=0,sum2=0;
for(int i=0;i<n;i++){
cin>>a[i];
sum1+=a[i];
}
for(int i=0;i<n;i++){
cin>>b[i];
sum2+=b[i];
}
int cnt=0;
for(int i=0;i<n;i++){
if(sum1+a[i]==sum2){
cnt++;
}
}
cout<<cnt<<endl;
return 0;
}
Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] a = new int[n];
int[] b = new int[n];
long sum1 = 0, sum2 = 0;
for (int i = 0; i < n; i++) {
a[i] = scanner.nextInt();
sum1 += a[i];
}
for (int i = 0; i < n; i++) {
b[i] = scanner.nextInt();
sum2 += b[i];
}
int cnt = 0;
for (int i = 0; i < n; i++) {
if (sum1 + a[i] == sum2) {
cnt++;
}
}
System.out.println(cnt);
}
}
Python
n = int(input())
a = list(map(int, input().split()))
b = list(map(int, input().split()))
sum1 = sum(a)
sum2 = sum(b)
cnt = 0
for i in range(n):
if sum1 + a[i] == sum2:
cnt += 1
print(cnt)
第二题:切割绳子
在线测评链接:http://121.196.235.151/p/P1161
题目描述
ak机有nnn 根绳子,从1编号到nnn,每根绳子长度为aia_iai。
ak机希望他的绳子们长度相等,他最多切kkk次绳子。
具体的:每次选择一个长度为sss的绳子,将其切成长度分别为x,yx,yx,y的两段
满足:x,yx,yx,y均为正整数,且x+y=sx+y=sx+y=s。
他想知道他是否可以切不超过kkk次绳子,从而满足条件,请你帮帮他吧。
输入描述
本题有多组测试数据,输入包含若干行。
第一行一个正整数 T(1≤T≤104)T(1\le T\le 10^4)T(1≤T≤104),表示数据组数。
接下来,对于每组测试数据:
第一行两个正整数n(1≤n<105),k(0≤k≤1014)n(1 ≤n< 10^5),k(0\le k\le 10^{14})n(1≤n<105),k(0≤k≤1014),分别表示ak机拥有的绳子个数,以及最多的操作次数。
第二行nnn个正整数ai(1≤ai≤109)a_i(1\le a_i\le 10^9)ai(1≤ai≤109),表示每根绳子的长度。
(保证所有测试数据中nnn的总和不超过10510^5105。)
输出描述
输出包含TTT行,对于每个测试数据,如果ak机可以做到让绳子一样长输出“YES’否则输出“NO”(不包含双引号)。
样例
输入
2
3 4
1 3 2
4 1
2 2 2 3
输出
YES
NO
说明
第一个测试数据:
将第二根绳子切两刀变为:1,1,1,将第三根绳子切一刀变成1,1。此时所有绳子长度都为1
第二个测试数据:
无方案可以满足条件
思路:数论 最大公约数
题目要求最终所有的绳子长度都相等,那么我们首先考虑,对于两个绳子来说,如何能保证最终能切成若干个长度相等的绳子?
比如长度为4,24,24,2的两根绳子,我们可以切分一次得到2,2,22,2,22,2,2
长度为8,28,28,2的两根绳子,我们可以切分3次得到2,2,2,2,22,2,2,2,22,2,2,2,2
长度为3,23,23,2的两根绳子,我们可以切分3次得到1,1,1,1,11,1,1,1,11,1,1,1,1
显然,从上面三个例子我们可以得出:对于两个长度分别为a,ba,ba,b的绳子,最终一定可以切分成若干个长度为ccc的绳子,且一定满足a%c=0且b%c=0a \%c=0且 b \% c=0a%c=0且b%c=0
也就是说,ccc是a,ba,ba,b的约数。那么对于最小分割次数来说,一定是切分成a,ba,ba,b的最大公约数
对应的分割次数为agcd(a,b)−1+bgcd(a,b)−1\frac{a}{gcd(a,b)}-1+\frac{b}{gcd(a,b)}-1gcd(a,b)a−1+gcd(a,b)b−1
那么,以此类推,nnn个绳子来说,就是求这nnn个数的最大公约数,然后按照上述分割次数的计算公式求解
我们使用辗转相除法来求最大公约数,对应的复杂度为lognlognlogn
总的时间复杂度为O(nlogn)O(nlogn)O(nlogn)
C++
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],n;
long long k;
int gcd(int x,int y){ //辗转相除法求最大公约数
return y==0 ? x:gcd(y,x%y);
}
int main(){
int T;
cin>>T;
while(T--){
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
int c=a[1];
for(int i=2;i<=n;i++){
c=gcd(c,a[i]);
}
long long cnt=0; //需要的操作次数
for(int i=1;i<=n;i++){
cnt+=(a[i]/c)-1;
}
if(cnt>k){
cout<<"NO"<<endl;
}else{
cout<<"YES"<<endl;
}
}
return 0;
}
Java
import java.util.*;
public class Main {
static final int N = 100010;
static int[] a = new int[N];
static int n;
static long k;
// 辗转相除法求最大公约数
static int gcd(int x, int y) {
return y == 0 ? x : gcd(y, x % y);
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int T = scanner.nextInt();
while (T-- > 0) {
n = scanner.nextInt();
k = scanner.nextLong();
for (int i = 1; i <= n; i++) {
a[i] = scanner.nextInt();
}
int c = a[1];
for (int i = 2; i <= n; i++) {
c = gcd(c, a[i]);
}
long cnt = 0; // 需要的操作次数
for (int i = 1; i <= n; i++) {
cnt += (a[i] / c) - 1;
}
if (cnt > k) {
System.out.println("NO");
} else {
System.out.println("YES");
}
}
}
}
Python
def gcd(x, y): # 辗转相除法求最大公约数
return x if y == 0 else gcd(y, x % y)
T = int(input())
for _ in range(T):
n, k = map(int, input().split())
a = list(map(int, input().split()))
c = a[0]
for i in range(1, n):
c = gcd(c, a[i])
cnt = 0 # 需要的操作次数
for i in range(n):
cnt += (a[i] // c) - 1
if cnt > k:
print("NO")
else:
print("YES")
第三题:武器强化
在线测评链接:http://121.196.235.151/p/P1162
题目描述
ak机有一把武器,初始攻击力为0。现在ak机面前有nnn个强化石,都可以用于强化该武器,每个强化石有一个强化上限aia_iai。
具体的,如果ak机便用第iii个强化石强化他的武器,那么ak机首先需要选择一个在强化上限以内的非负整数xxx作为强化系数,假设ak机当前武器攻击力为kkk,那么会变为k∣xk|xk∣x(其中|表示按位或操作)。接着第iii个强化石在使用后会失效,即无法再次使用。
ak机想知道,这nnn个强化石最多能使他的武器攻击力强化为多少。
输入描述
本题包含多组测试数据。
第一行输入一个正整数T(1≤T≤104)T(1\le T\le 10^4)T(1≤T≤104),表示测试数据组数
接下来,对于每组测试数据,输入包含两行
第一行一个正整数n(1≤n≤2×105)n(1\le n\le 2\times 10^5)n(1≤n≤2×105),表示可以使用的强化石个数
第二行nnn的整数ai(0≤ai≤109)a_i(0\le a_i\le 10^9)ai(0≤ai≤109),表示每个强化石的强化上限。(保证所有测试数据中nnn的总和不超过2×1052\times 10^52×105)
输出描述
输出包含TTT行,表示每组测试数据的答案
对于每组测试数据,输出一行一个整数,表示ak机的武器能达到的最大攻击力。
样例
输入
2
5
2 3 3 3 6
3
1 0 0
输出
7
1
思路:贪心 位运算
本题我们需要对数字按二进制位进行拆解,然后利用或运算的特性来处理
我们知道,对于二进制的第iii位有
0|0=00|1=11|1=1
也就是说,我们需要找到一个尽可能高的位置,让其值置为1,这样k∣xk|xk∣x就可以把kkk最大化
以k=1k=1k=1为例
如果x=2=(10)2x=2=(10)_2x=2=(10)2,第二位为1,因此k∣x=(11)2=3k|x=(11)_2=3k∣x=(11)2=3
如果x=4=(100)2x=4=(100)_2x=4=(100)2,第三位为1,则有k∣x=(101)2=5k|x=(101)_2=5k∣x=(101)2=5
如果x=5=(110)2=6x=5=(110)_2=6x=5=(110)2=6,前三位都为1,则有k∣x=(111)2=7k|x=(111)_2=7k∣x=(111)2=7
因此,我们可以发现
- 如果xxx的第iii位为1,那么k∣xk|xk∣x之后,kkk的第iii位也为1
- 如果xxx的前iii位都为1,那么k∣xk|xk∣x之后,kkk的前iii位也都为1
我们可以首先预处理出aia_iai的所有位置i(0≤i≤31)i(0\le i\le 31)i(0≤i≤31)中含有1的个数,存到一个32位的数组cntcntcnt中
然后我们从高到低枚举位置,如果当前位置cnt[i]>0cnt[i]>0cnt[i]>0,那么首先我们可以让k∣=2ik|=2^ik∣=2i,这样就可以将第iii位设置为1,并且将计数-1
如果当前位置仍然有cnt[i]>0cnt[i]>0cnt[i]>0,那么我们可以将k∣=(2i−1)k|=(2^i-1)k∣=(2i−1),这样就可以将前i−1i-1i−1位都置为1
最终输出kkk即可。
整体的时间复杂度为O(32n)O(32n)O(32n)
C++
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
void solve() {
int n;
cin >> n;
vector<int> cnt(31, 0);
for (int i = 0, x;i < n;++ i) {
cin >> x;
for (int j = 0;j < 31;++ j) if (x >> j & 1) {
++ cnt[j];
}
}
int ans = 0;
for (int i = 30; i>=0; -- i) {
if (cnt[i]) {
ans |= 1 << i;
-- cnt[i];
}
if (cnt[i]) {
ans |= (1<<i) - 1;
break;
}
}
cout << ans << endl;
}
int main() {
int T = 1;
cin >> T;
while(T --) {
solve();
}
}
Java
import java.util.Scanner;
import java.util.Arrays;
public class Main {
static int T;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
T = scanner.nextInt();
while (T-- > 0) {
solve(scanner);
}
}
public static void solve(Scanner scanner) {
int n = scanner.nextInt();
int[] cnt = new int[32]; //统计每一位1的个数
Arrays.fill(cnt, 0);
for (int i = 0; i < n; ++i) {
int x = scanner.nextInt();
for (int j = 0; j < 32; ++j) {
if ((x >> j & 1) == 1) { //如果第j位为1
++cnt[j]; //计数+1
}
}
}
int ans = 0;
for (int i = 31; i >= 0; --i) {
if (cnt[i] > 0) { //如果第i位为1,则直接对2^i进行或操作
ans |= 1 << i;
--cnt[i];
}
if (cnt[i] > 0) { //如果还有1,则对(2^i-1)进行或操作
ans |= (1 << i) - 1;
break;
}
}
System.out.println(ans);
}
}
Python
T = int(input().strip())
for _ in range(T):
n = int(input().strip())
cnt = [0] * 32 #统计每一位1的个数
w=list(map(int,input().split()))
for x in w:
for j in range(32):
if x >> j & 1: #如果第j位为1
cnt[j] += 1 #计数+1
ans = 0
for i in range(31, -1, -1):
if cnt[i]: #如果第i位为1,则直接对2^i进行或操作
ans |= 1 << i
cnt[i] -= 1
if cnt[i]: #如果还有1,则对(2^i-1)进行或操作
ans |= (1 << i) - 1
break
print(ans)
5万+

被折叠的 条评论
为什么被折叠?



