AcWing第57场周赛

题目列表

A AcWing 4485. 比大小

题目描述

给定一个长度为 n 的数组 a1,a2,…,an 和一个长度为 n 的数组 b1,b2,…,bn。
请你计算,数组 a 的所有元素之和是否大于或等于数组 b 的所有元素之和。
输入格式
第一行包含整数 n。
第二行包含 n 个整数 a1,a2,…,an。
第三行包含 n 个整数 b1,b2,…,bn。
输出格式
如果数组 a 的所有元素之和大于或等于数组 b 的所有元素之和,则输出 Yes,否则输出 No。
数据范围
前三个测试点满足 1≤n≤5。
所有测试点满足 1≤n≤50,0≤ai,bi≤1000。
输入样例1:
5
1 2 3 4 5
2 1 4 3 5
输出样例1:
Yes
输入样例2:
5
1 1 1 1 1
1 0 1 0 1
输出样例2:
Yes
输入样例3:
3
2 3 9
1 7 9
输出样例3:
No

分析

签到题,将a数组之和减去b数组之和与0比较即可。

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int main(){
    int n,x;
    scanf("%d",&n);
    int res = 0;
    for(int i = 0;i < n;i++) {
        scanf("%d",&x);
        res += x;
    }
    for(int i = 0;i < n;i++) {
        scanf("%d",&x);
        res -= x;
    }
    if(res >= 0)    puts("Yes");
    else    puts("No");
    return 0;
}

B AcWing 4486. 数字操作

题目描述

给定一个整数 n,你可以对该数进行任意次(可以是 0 次)变换操作。
每次操作为以下两种之一:
将整数 n 乘以任意一个正整数 x。
将整数 n 替换为 n√(执行此操作的前提是 n√ 为整数)。
请你计算,通过上述操作,n 能达到的最小可能值,以及达到最小可能值所需要的最少操作次数。
输入格式
一个整数 n。
输出格式
一行,两个整数,分别为 n 能达到的最小可能值,以及达到最小可能值所需要的最少操作次数。
数据范围
所有测试点满足 1≤n≤106
输入样例1:
20
输出样例1:
10 2
输入样例2:
5184
输出样例2:
6 4

分析

本题考察算术基本定理和质因数分解。对于任意一个正整数n一定可以拆分为若干个质数相乘,即n = p1a1 * p2a2 * … * pkak。其中pi表示质数。对于n,乘上正整数x的操作会使得n增大,所以为了减小n的值只有依靠开方操作,而只有在n是平方数的前提下才能对n开方,所以乘法操作的作用就是将n变成平方数。

首先开方操作对于写成质因子相乘式子的n而言,等价于将每个质数的幂都减半,比如36 = 22 * 32,对36开方就得到6 = 21 * 31。换而言之,如果n是平方数,那么n根据算术基本定理的展开式中每个质数的幂次一定是偶数的,否则无法开方。

周赛时我想到的第一个解法就是模拟,比如n为5184时,一开始就是平方数,开平方得到72 = 23 * 32,由于2的幂是3,所以不是平方数,我们给n乘上2变成144,这样2的幂就变成了4,可以继续开方得到12 = 22 * 3,乘上3再次开方得到6,6只含2,3两个质因子,并且幂都是1,不能继续减小了。这也可以得出本题的另一个性质,n经过若干次操作后得到的最小可能值一定是n的所有质因子之积。

上面的模拟过程开了3次平方根,乘法操作也有2次,一共是5次操作,但是最少操作次数却是4次,这是因为乘法操作可以合并为一次,开方操作需要满足n是平方数的条件,但是乘法操作没有条件,只要一开始将n的所有质因子的幂都提高到某个2的整数次方,后面只需要开方操作就可以实现目的了。

还是以5184为例,5184 = 26 * 34,最高次幂是6,需要将其提升到最近的2的整数次幂8,3的幂也提升到8,也就是第一步将n乘上 22 * 34,然后对新的n连续开方3次就得到了最终结果6了,这样操作的最小次数就是4次。

本题虽然只用到了数论中的简单知识,但是周赛时完全推出本题的性质并且写出来也是不容易的。梳理下本题的解题步骤:首先要对n分解质因数,能够化成的最小可能值就是n的所有质因子之积。然后就是求出n的质因子中最大的幂,如果它不是2的整数次方,就要将其提升到比它大的最小的2的整数次方。具体乘上多少不需要我们考虑,但是需要判断一开始n的质因子的幂是否都相等并且等于2的整数次幂,如果是,就不需要做第一次乘法操作了。然后加上开方的次数就是最小的操作次数了。

代码

#include <iostream>
#include <vector>
#include <cstdio>
#include <algorithm>
using namespace std;
vector<int> cnt;
int main(){
    int n;
    scanf("%d",&n);
    int res = 1,m = 0;
    for(int i = 2;i * i <= n;i++){
        if(n % i == 0){
            res *= i;
            int t = 0;
            while(n % i == 0){//统计质因子的幂
                t++;
                n /= i;
            }
            cnt.push_back(t);
            while(1 << m < t)   m++;//统计不小于幂的最小2的整数次幂,比较巧妙
        }
    }
    if(n > 1){//剩下的最大的质因子
        res *= n;
        cnt.push_back(1);
        while(1 << m < 1)   m++;
    }
    for(auto x : cnt){
        if(x < 1 << m){//如果有幂不等于2^m的幂,就需要加上乘法操作
            m++;
            break;
        }
    }
    cout<<res<<" "<<m<<endl;
    return 0;
}

C AcWing 4487. 最长连续子序列

题目描述

给定一个长度为 n 的整数序列 a1,a2,…,an。
现在,请你找到一个序列 a 的连续子序列 al,al+1,…,ar,要求:
al + al+1 + … + ar>100×(r−l+1)。
连续子序列的长度(即 r−l+1)尽可能大。
请你输出满足条件的连续子序列的最大可能长度。
输入格式
第一行包含整数 n。
第二行包含 n 个整数 a1,a2,…,an。
输出格式
一个整数,表示最大可能长度。
如果满足条件的连续子序列不存在,则输出 0。
数据范围
前三个测试点满足 1≤n≤5。
所有测试点满足 1≤n≤106,0≤ai≤5000。
输入样例1:
5
100 200 1 1 1
输出样例1:
3
输入样例2:
5
1 2 3 4 5
输出样例2:
0
输入样例3:
2
101 99
输出样例3:
1

分析

首先看下题目中的单调性。

如果用f[i]表示以i开头的满足条件的连续子序列的最大可能,假设这个最长子序列长度是n,那么从i出发,长度为n - 1的子序列是否一定满足条件呢?显然是不一定的,比如1 300,最长连续是子序列1 300,但是去掉300后的子序列1的平均值是小于100的,不满足条件。

如果我们直接二分最大长度呢?如果不存在满足条件的长度为n的子序列,是否一定不存在满足条件长度为n + 1的子序列,也是不一定的,比如102 97 102,长度为2的子序列都是不满足条件的,如果我们二分长度到2发现不满足条件,就减少长度,但是实际上长度为3的子序列102 97 102的平均值是大于100的,满足条件,所以这里也没有单调性。

再来考虑本题能否使用01分数规划来求解呢?01分数规划问题一般是求一个分数表达式的最值问题,比如AcWing 234 放弃测试AcWing 361 观光奶牛这两个问题。本题虽然不具有分数规划问题的典型特征,但是也不妨碍我们对原表达式进行变形,用类似的方法来求解。al + al+1 + … + ar > 100×(r−l+1)变形得到∑(ai - 100)> 0,显然我们可以令bi = ai - 100,只要bi的连续子序列之和大于0,那么这个连续子序列就是满足条件的。由于本题不是求分数的最值,所以变形后仍然不具备单调性,对表达式进行变形仅仅是为了简化运算,我们需要人为的构造单调性。

现在我们要对b数组求满足条件的最长连续子序列的长度,可以先求下b数组的前缀和s,正常情况下我们要求以i为末尾的最长连续子序列的长度,需要枚举子序列的开头,逐步增加序列的左端点,直到满足条件,不妨列出来看一下:

s0, s1, s2, …,si-1

我们先判断si - s0是否大于0,如果是,则最大长度就是i,不是则接着判断si - s1,可以发现,对于固定的si,如果想要si - sj最大,需要sj最小,也就是左端点的值sj越小,si - sj越可能大于0,如果s0 < s1,那么s1必然不可能是最优解,因为在si - s0 > 0时,满足条件,找到了长度为i的合法序列,那么长度为i - 1的合法子序列必然表示最优解了;在si - s0 <= 0时,由于s1>s0,则si - s1 < si - s0<=0,也不是合法的解,所以从s0到si-1我们只需要一路保存一些递减的sj即可,也就是使用单调栈来保存si左边从左到右递减的sj。

我们来看下遍历到b数组中第i个元素时的情况,首先与栈顶元素比较下,如果s[i]大于栈顶元素,那么i不可能作为后面元素最长子序列的左端点了,也就不需要加入栈中,但是既然s[i]大于栈顶元素,说明在栈中可以找到使得si - sj > 0的j了,这时候我们就可以使用二分法来查找最长子序列的左端点了,如果在栈中二分到的sj满足条件,说明最优解的长度不小于此时的子序列的长度,如果二分到的sj不满足条件,那么由于栈中元素是递减的,栈中左边的元素也一定不满足条件,直接向右查找即可。

本题的难点在于即使是使用分数规划的方法变形后得到的新数组b依然不具备单调性,需要继续构造前缀和才能发现单调性。

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1000005;
int n,a[N],stk[N];
long long s[N];
int main(){
    scanf("%d",&n);
    for(int i = 1;i <= n;i++) {
        scanf("%d",&a[i]);
        s[i] = s[i-1] + a[i] - 100;
    }
    int tt = -1,res = 0;
    stk[++tt] = 0;
    for(int i = 1;i <= n;i++){
        if(s[i] < s[stk[tt]]) stk[++tt] = i;
        else if(s[i] > s[stk[tt]]){
           int l = 0,r = tt;
            while(l < r){
                int mid = l + r >> 1;
                if(s[i] - s[stk[mid]] > 0)  r = mid;
                else    l = mid + 1;
            }
            res = max(res,i - stk[l]); 
        }
    }
    cout<<res<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值