acm校内选拔题解

第一题 小D的for循环

题意:对于两个给定的正整数 n,p,计算2的2n次方mod p的结果。其中1<=n<=1e6,1<=p<=231-1.

考场做法:当时以为第一道题是签到题,加上比较紧张脑子都不转的,以为是个快速幂的板子题直接敲了个快速幂取模的板子就交了,然后发现2n次方这个数不应该对p取模,因为它是指数,取模的话不能保证是原数,例如25mod 4≠21 mod 4。然后我又重写了一个不取模的快速幂板子,结果发现2n这个数很大,直接爆掉longlong了,然后思维就被引导去把这个数搞出来,因为不好搞没啥思路就直接跳过这道题了,很可惜的是到最后也没有做出来。
正解:其实在考场上也注意到了这个东西就是:底数平方可以让指数除2(快速幂原理),但最后我脑抽了又把那一大串表达式化回去了(,根据这个东西,底数平方一次指数就除以2,所以底数n次平方过后指数2n就化为了1,结果就是此时的底数。
代码:非常简单,但就是没写出来(

#include<iostream>

using namespace std;
typedef long long ll;

int main() {
    ll n,p;
    cin >> n >> p;
    ll ans = 2;
    for(int i = 1;i <= n;i++) {
        ans = (ans * ans) % p;
    }
    cout << ans << endl;
}

第二题 羽球人的倔强

题意:给出两个正整数n,m代表有n种球,m个人,然后给出n个整数代表第二行共有 n 个正整数代表每种球的价格,再给出m个正整数代表每个人所拥有的钱,每个人都会买自己能买的最贵的球,问最后他们能剩下来的钱的最小值。

考场做法及正解:这个题直接枚举每个人求出每个人剩下的钱最后加起来即可,考试的时候以为是n个球,每种球只有一个,写了个双指针交上去结果wa了,当时直接怀疑人生,加上当时已经花了二十多分钟了,一看排名二十名开外,敲键盘的手都开始抖起来了,最后再读题发现每种球无限多个,这样的话对枚举到的每个人,可以直接暴力求出他买的哪种球,也可以用二分来做,算是本场最简单的题了吧。

第三题 讨厌的字符串

题意:小L在研究字符串的时候遇到一个麻烦,他的手里有一个字符转换集,里面记录了可以相互转换的字符以及转换所需要的代价(注意:转换是单向的),他的目的是把字符串Str1转换成字符串Str2,而他又不想花费过多的代价,所以他找到了聪明的你来帮他解决这个问题。
输入格式
第一行输入一个整数n(1<=n<=2e5),表示两个字符串的长度;
接下来行输入两个字符串Str1和Str2;
第三行输入一个整数m(1<=m<=2e5),代表后面要输入的行数;随后m行,每行输入两个字符c1,c2和一个正整数x(0<=x<=1e9),代表第一个字符转换到第二个字符所产生的代价。
输出格式
输出一个整数,表示转换的最小代价或者输出impossible表示无法将完成字符串的转换。

考场做法:看到字符串就放弃了(,感觉是图论题,每个字符都是一个节点,转换关系是边,但是在建图一步就放弃不想写了(。
正解:由于节点数为常数级别,所以可以用二维数组存图+弗洛伊德求最小代价,gra[i][j]表示字符i到字符j所花费的代价,求出字符之间转换的最小代价后就可以遍历字符串累计代价了,若遇到inf则说明无法完成转换,最后记得判重。
代码

#include<bits/stdc++.h>

using namespace std;
long long gra[128][128]={0};
char a[250007]={0},b[25000750]={0};
int main()
{
    int n;
    cin>>n;

    for(int i=0;i<n;i++)cin>>a[i];
    for(int i=0;i<n;i++)cin>>b[i];

    for(int i=0;i<28;i++)for(int j=0;j<28;j++)gra[i][j]=1234567898765;
    for(int i=0;i<26;i++)gra[i][i]=0;
    int m;
    cin>>m;
    for(int i=0;i<m;i++){
        char x,y;
        int z;
        cin>>x>>y>>z;
        if(z<gra[x-'a'][y-'a'])
            gra[x-'a'][y-'a'] = z;
    }
    for(int k=0;k<26;k++){
        for(int i=0;i<26;i++){
            for(int j=0;j<26;j++){
                gra[i][j]=min(gra[i][k]+gra[k][j],gra[i][j]);
            }
        }
    }
    long long ans=0;
    for(int i=0;i<n;i++){
        int s=a[i]-'a',d=b[i]-'a';
        if(gra[s][d]==1234567898765){
            cout<<"impossible";
            return 0;
        }
        else ans+=gra[s][d];
    }
    cout<<ans;
    return 0;
}

第四题 你是DS高手吗?

题意:小 D 最近痴迷于 DS , 所谓 DS , 在算法竞赛中的全称是 DataStructure , 也就是数据结构的意思,他所遇到的习题是这样的,在 log 级别的时间复杂度内查询一个区间内有多少个数是小于等于 x 的,小 D 很快就学会了这个算法,但是他并不满足,他思考,怎么快速求出一个区间内有多少个数是等于 x 的呢,他好像被难住了,你能帮帮他吗?
输入格式
第一行,两个正整数 n , q , 表示数组的长度和查询的次数。
第二行,n 个数,其中第 i 个数表示 a[i] 。
接下来 q 行,每行三个数 l, r, x 表示询问。其中 1<=n<=1e6,1<=q<=1e6,1<=a[i]<=1e6,1<=l<=r<=1e6,1<=x<=1e6。
输出格式
对于每个询问,输出一个整数,表示 [l,r] 区间内有多少个数等于 x 。

考场做法:在log级别查询区间中等于x的数的个数,当时想着log级别维护区间,这不得用线段树吗,但是我不会线段树,还没学(,思考了一会有无不用高级数据结构的方法,笨脑子思考不出来嘞。
正解:x的值在1e6以内,这个数据范围刚好可以开hash数组(值存储为键),也算是一种暗示了吧。开1e6个vector,其中存储原数组中值为x的下标,由于原数组是从小到大遍历的,所以每个vector内存储的下标是有序的,这样就凑出了使用二分的条件,写两个二分统计里面有多少在查询区间内的数就行了。(以前好像见过这题,没想到再见还是不会写)。
代码

#include<iostream>
#include<vector>
using namespace std;
const int N = 1e6+7;
vector<int> a[N];

int main() {
    int n,q;
    cin >> n >> q;
    int x;
    for(int i = 1;i <= n;i++) {
        scanf("%d",&x);
        a[x].push_back(i);
    }

    int l1,r1;
    int a1,a2;
    while(q--) {
        scanf("%d %d %d",&l1,&r1,&x);
        int l = 0,r = a[x].size()-1;
        while(l<=r) {
            int mid = (r+l)>>1;
            if(a[x][mid]>=l1) r = mid - 1;
            else l = mid + 1;
        }
        a1 = l;
        l = 0,r = a[x].size()-1;
        while(l<=r) {
            int mid = (r+l)>>1;
            if(a[x][mid]>r1) r = mid - 1;
            else l = mid + 1;
        }
        a2 = r;
        cout << a2 - a1 + 1 << endl;
    }

    return 0;
}

第五题 火焰冰霜还有奥术

题意:小z正在玩一种卡牌游戏。
他有x张火焰法术,y张冰霜法术,z张奥术法术。每张法术有固定的伤害,但可以通过以下效果造成额外伤害:
当你使用火焰法术时,如果你使用的上一张法术是奥术法术,可以造成双倍伤害;
当你使用冰霜法术时,如果你使用的上一张法术是火焰法术,可以造成双倍伤害;
当你使用奥术法术时,如果你使用的上一张法术是冰霜法术,可以造成双倍伤害;
小z想知道,通过调整出牌顺序,将所有法术打出,最多可以造成多少伤害?
输入格式
首先输入三个整数x,y,z,分别表示火焰法术,冰霜法术,奥术法术的数量。接下来三行:
第一行输入x个整数a 表示第i张火焰法术造成的伤害。
第二行输入y个整数b 表示第i张冰霜法术造成的伤害。
第三行输入z个整数c 表示第i张奥术法术造成的伤害。
输出格式
输入一个整数,表示最多可以造成的伤害。

考场做法:想什么呢考场看到这个题肯定就直接润了啊,都没细读(
正解:看了题解发现这就是一个简单的模拟题(,首先要证明一个东西:当第一张卡牌是火后,之后的最优解就确定了:按照双倍的顺序从大到小将所有卡牌打出,如此,我们只需要分别求出三种情况(第一张火 第一张冰 第一张雷)就可以求出所能打出的最大伤害了。
代码

#include<iostream>
#include<algorithm>
using namespace std;
int x,y,z;
typedef long long ll;
const int N = 1e5+7;
ll a[N],b[N],c[N];

int main() {
    scanf("%d %d %d",&x,&y,&z);
    for(int i = 1;i <= x;i++) scanf("%d",&a[i]);
    for(int i = 1;i <= y;i++) scanf("%d",&b[i]);
    for(int i = 1;i <= z;i++) scanf("%d",&c[i]);
    ll ans1=0,ans2=0,ans3=0,res=0;
    sort(a+1,a+1+x);
    sort(b+1,b+1+y);
    sort(c+1,c+1+z);
    int i=x,j=y,k=z;
    int flag;
    //huo
    ans1 += a[1];
    flag = 1;
    while(i>1||j||k) {
        if(j>=1) {
            if(flag==1) ans1+=2*b[j];
            else ans1 += b[j];
            flag = 2;j--;
        }
        if(k>=1) {
            if(flag==2) ans1 += 2*c[k];
            else ans1 += c[k];
            flag = 3;k--;
        }
        if(i > 1) {
            if(flag==3) ans1 += 2*a[i];
            else ans1 += a[i];
            flag = 1;i--;
        }
        //cout << ans1 << endl;
    }
    //
    i=x,j=y,k=z;
    ans2 += b[1];flag = 2;
    while(i||j>1||k) {
        if(k>=1) {
            flag==2?ans2+=2*c[k]:ans2+=c[k];
            flag=3;k--;
        }
        if(i>=1) {
            flag==3?ans2+=2*a[i]:ans2+=a[i];
            flag=1;i--;
        }
        if(j>1) {
            flag==1?ans2+=2*b[j]:ans2+=b[j];
            flag=2;j--;
        }
    }
    //
    i=x,j=y,k=z;
    ans3 += c[1];flag=3;
    while(i||j||k>1) {
        if(i>=1) {
            flag==3?ans3+=a[i]*2:ans3+=a[i];
            flag = 1;i--;
        }
        if(j>=1) {
            flag==1?ans3+=b[j]*2:ans3+=b[j];
            flag=2;j--;
        }
        if(k>1) {
            flag==2?ans3+=c[k]*2:ans3+=c[k];
            flag=3;k--;
        }
    }
    res = max(ans1,ans2);
    res = max(res,ans3);

    cout << res << endl;

    return 0;
}

第六题 我不要看

题意:从奇怪的地方拿到了一个计数器,计数器每秒自增1,计数器遇到数位中含有数字4的数(比如4,14,114,114514等)时会直接跳过。计数器从0开始计数,现在给出计数器的示数,求真实经过的秒数。
输入格式:
一行,一个正整数,表示计数器读数t
输出格式:
一行,一个整数,表示计数器示数

考场做法:考试的时候原本只会暴力枚举,最后剩几分钟的时候在那看着样例硬猜答案,最后两分钟随便写了个做法上去没想到ac了(。我们可以求出最后一个4出现在每一位有多少种情况,按1919810为例,最后一个4出现在最后一位时有191981-1中情况,最后一个4出现在倒数第二位时,有(19198-1)*9中情况,倒数第三位时,有(1919-0)99种情况,照这个规律求最后一个4在每一位的贡献即可。

第七题 我不要听

题意:从神奇的地方掏出了一个数字n,如果n恰好是两个质数的乘积,从小到大输出这两个质数,否则输出两个−1
输入格式:
一行,一个正整数n
输出格式:
一行,两个整数用空格隔开
数据范围 n≤1012

考场做法:vocal当时我一度以为自己写的很对(欧拉筛+二分),虽然n在1e12,但是拆分成两个数我以为两个数都会在1e6以内,所以我的做法是先筛出1e6以内的质数,然后用定一选一(二分选)的方法看看是否会有两个质数相乘,后面只对了一半的样例,细细思考发现并不一定两个数都在1e6范围内。
正解:n在1e12,但是枚举因子可以只枚举到根号n,所以时间复杂度是够的(并不一个两个质数都在1e6范围内,但必有一个质数在1e6范围内),可以将n的所有质因数放进一个容器中,如果最后容器内有两个数则说明该数的因数只有两个且两个因数不能再分解(分解的因子也会被放进容器),所以为质因数,此时输出两个数否则输出-1 -1.
代码

#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
ll n;
vector<int> a;

int main() {
    cin >> n;
    for(ll i = 2;i * i <= n;i++) {
        if(n%i==0) {
            a.push_back(i);
            a.push_back(n/i);
        }
    }
    if(a.size()==2) cout << a[0] << " " << a[1] << endl;
    else cout << -1 << " " << -1 << endl;

    return 0;
}

第八题 萨卡班甲鱼

题意:萨卡班甲鱼在游泳池里游泳,游泳池可以看作一个二维网格,甲鱼每次可以从当前所在格子游到上、下、左、右中的某个格子。网格的横坐标范围是[0,n),纵坐标范围是[0,m),甲鱼初始时在坐标(0,0)位置,由于甲鱼的游泳技术很糟糕,它不能游到横纵坐标数位和大于k的位置。请输出甲鱼从(0,0)出发可以游到多少个不同的位置((0,0)也算一个位置)。
横纵坐标数位和:横坐标的数位和+纵坐标的数位和。
数位和:数的每一位数字加起来,1的数位和是1,14的数位和是1+4=5,114的数位和是1+1+4=6。
输入格式:
一行,三个正整数n,m,k
输出格式:
一行,一个整数

考场做法:如果没有这最后两个送分题不仅送分而且给了我自信,让我敲键盘的双手不再颤抖,我真进不去集训队了www,简单搜索题dfs即可,略(当时代码本地样例不通过,但提交上去ac了(。

第九题 搬书

题意:(以下内容纯属虚构)每天晚自习下课后都会有一些粗心的同学将书遗落在教室里,这一天轮到了小A去打扫教室,他需要将每个教室的书都搬到一楼的失物招领处。生活部有一个奇怪的规定:轮到打扫教室的同学必须在每一层楼都选一间教室进行打扫(包括一楼)。搬书是一个很累的活,小A不想搬太多的书,因此他需要你帮他找一种选择教室的方案,每层选一间教室进行打扫,使得转移的书总数最小。为了少走一点路,他希望你帮他选择的教室在上下层必须是相邻的(即第n−1层楼的教室必须在第n层楼的教室正下方或是与其正下方教室相邻,如果是第一间教室,那么在下一层所选择的教室只能是正下方或是右下方的,如果是最后一间,那么在下一层所选择的教室只能是正下方或是左下方的)。小A会告诉你教学楼的层数,以及每层楼的教室数量。而你只需要告诉小A最小的搬书数量即可。
输入格式:
第一行是两个整数m,n,代表有m层楼,每层楼有n间教室。
接着从顶层到第一层依次给出每层楼的教室中所遗落的书的数量。
一共m行,每行有n个整数a 代表这层楼的第i间教室里被遗落书的数量。
数据间以空格进行分割。

考场做法:简单dp题,和数字三角形挺像,略。

  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秭归云深处

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值