2021年春季ACM训练赛第4场

问题 A: 提取年份

题目描述
小明想从一段英文短文中提取潜在的年份信息,待匹配的年份的范围为1000年至3999年,包含1000和3999。
输入一段英文短文,按出现次序输出所提取到的所有可能的年份字符串。
【注意:待提取的年份字符串必须是连续的、长度为四位的数字,且这个四位数后面不能再有数字出现,前面也不能有非0数字出现,例如“12345”是不满足要求的年份字符串,但是“01234”是满足要求的年份字符串。】

输入
单组输入,输入一段英文短文。(不超过2000个字符)

输出
输出所提取到的所有可能的年份字符串,两两之间用一个空格隔开。

样例输入

And millionaires will hold 46% of total wealth by 2019, the report says. This ratio is likely to increase in 2020.

样例输出

2019 2020

分析: 其实这道题并不是很复杂,可能大家都考虑得没有那么清楚。每一个数字串的长度不一定是4才能够去组成年份。例如题目说的“01234”也是满足要求的,甚至“001234”也是可以的,仔细看清楚题目的注意部分。所以我们可以每次取出一个数字串,将它变成整形数据同时去除前导0,最后判断年份是不是在1000到3999之间即可。

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;

int main(){
    string s;getline(cin,s);
    int num=0;
    for(int i=0;i<s.size();i++){
        if(isdigit(s[i]))num*=10,num+=s[i]-'0';
        else{
            if(num>=1000&&num<=3999)cout<<num<<" ";
            num=0;
        }
    }
    cout<<endl;
    return 0;
}

问题 B: 小h的数字

题目描述
小h正在做一项调查:您最喜欢从0到9的10个数字中的哪个? 他问了n个人,这些人告诉的数字形成了一个n位数的整数。 然后小h打电话给女友,请求帮忙数一下,哪个数字出现在这个整数中最多。

输入
多组输入,每组占一行,由一个n位的整数构成
题目保证:0≤n≤10^1000

输出
对于每组输入,输出出现次数最多的数字(如果有多个数字出现次数相同,则输出最小的数字)

样例输入

1
2
3

样例输出

1
2
3

分析: 这道题比较简单,这个n位的整数必须用字符串来存,遍历字符串,记录每一个数字的出现次数,最后比较出出现最多的数字即可。

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int num[15];
int main(){
    string s;
    while(cin>>s){
        memset(num,0,sizeof(num));
        for(int i=0;i<s.size();i++){
            num[s[i]-'0']++;
        }
        int ans=1;
        for(int i=1;i<=9;i++){
            if(num[i]>num[ans])ans=i;
        }
        cout<<ans<<endl;
    }
    return 0;
}

问题 C: 小h的仓库

题目描述
在一个空地上,人们想要建造一个仓库。 该空地可以看作一个二维平面。
您需要使用边(这些边必须是水平或垂直的,长度为1,或者倾斜45度,长度√2)围成一个封闭的多边形。
多边形的顶点必须位于整数点, 请求出可以围成的多边形的最大面积。

输入
包含多组输入,每组数据给出一个数n(3≤n≤2*10^9)

输出
对于每组数据,输出可以围成的多边形的最大面积,答案保留1位小数。

样例输入

3
4
5
6

样例输出

0.5
2.0
2.5
4.0

分析: 数学题,找规律,很难,规律非常难找
在这里插入图片描述
在这里插入图片描述
我们组成的每一个图形都能由三角形来表示,每一个三角形的面积都是0.5,我们要找的就是k和三角形之间的关系。
K除以4的商P,意思就是能够先组成一个边长为P的正方形。
组成了正方形之后,如果余数为1,那么我们可以从组成的正方形种拆出P条边出来,一起组成2*p-1个三角形。这里需要在纸上画一画比较好理解。或者在列出几组数据之后,发现余下的三角形形成了1,0,1,0,3,0,3,0的循环。那么也可以直接通过规律写代码了。
如果余数为2,那我们可以在刚刚组成的边长为P的正方形种拆出P条边,和这两条边组成一个长为P+1,宽为P的长方形。
如果余数为3,先拿两根和余数为2的情况一样,组成一个长为P+1,宽为P的长方形。剩下一根的组成和余数为1的情况类似。
K或者s为奇数时,面积存在.5。K为偶数时,面积为整数。
这道题在草稿纸上多画画,画出一些结果出来。找规律

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;

int main()
{
    ll n;
    while(~scanf("%lld",&n)){
        ll p=n/4,v=n%4,s;
        if(v==0)s=4*p*p;
        else if(v==1)s=4*p*p+(2*p-1);
        else if(v==2)s=4*p*p+4*p*1;
        else if(v==3)s=4*p*p+4*p*1+(2*p+1);
        if(n%2==0)printf("%lld.0\n",s/2);
        else printf("%lld.5\n",s/2);
    }
    return 0;
}

问题 D: 小h的餐厅

题目描述
小H和朋友们来到一家餐厅,餐厅规则比较奇怪:

1.每个人有不同的折扣上限(单人从总结里折算的最高金额)

2.超过折扣上限的部分原价付费(n个人可以每人出一部分)

小H和朋友们一共个人来到这家餐厅吃饭,他们点的菜品总金额为T

现在告诉你每个人的折扣率pi和折扣上限mi,请告诉他们最少需要支付多少钱?

输入
多组输入。
每组输入第一行两个整数n,T,分别表示人数和菜品总金额。
接下来n行,每行两个数字pi和mi(0<n<100)。

输出
对于每组数据,输出一个答案,答案向下取整。

样例输入

2 100
0.7 70
0.6 50
3 500
0.6 100
0.8 200
0.7 100
1 100
0.6 100

样例输出

65
390
60

分析: 贪心问题,要使支付的费用最少,那么需要优先将折扣率小的优先打折使用,直到支付原价付费的价格达到T元。如果所有人的折扣上限和达不到T元,最后还需要加上剩下的需要原价支付的金额。

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
struct node{
    double p,m;
}a[105];
int cmp(const node &a,const node &b){
    return a.p<b.p;
}
int main(){
    int n,t;
    while(~scanf("%d %d",&n,&t)){
        for(int i=1;i<=n;i++)scanf("%lf %lf",&a[i].p,&a[i].m);
        sort(a+1,a+1+n,cmp);
        double sum=0;
        for(int i=1;i<=n;i++){
            if(t>=a[i].m)t-=a[i].m,sum+=a[i].m*a[i].p;
            else{
                sum+=a[i].p*t,t=0;
                break;
            }
        }
        sum+=t;
        printf("%d\n",(int)sum);
    }
    return 0;
}

问题 E: 小h的绳子

题目描述
小H有一根长度为n的绳子,需要用来组成一个矩形(不能算正方形),矩形每一边的长度都是整数。

可以将绳子剪成四个部分,作为矩形的四个边。请问可以组成多少个不同的矩形(不能算正方形)?

输入
多组输入,对于每组数据,输入一个整数,表示绳子长度(1≤n≤10^5)

输出
每组数据,输出一个答案,表示可以形成不同矩形的数量。

样例输入

6
20

样例输出

1
4

分析: 长度为n的绳子就是这个矩形的周长,那么矩形的长和宽就是n/2,所以如果n是奇数,那么不能构成矩形。否则,计算出长加宽等于n/2的矩形个数即可。注意不能算正方形。

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;

int main(){
    int n;
    while(~scanf("%d",&n)){
        if(n%2==1||n<4)cout<<0<<endl;
        else{
            if(n%4==0)cout<<n/4-1<<endl;
            else cout<<n/4<<endl;
        }
    }
    return 0;
}

问题 F: 耶路撒冷战役

题目描述
第二次十字军东征末期,由阿拉伯领袖萨拉丁率领的阿拉伯联军兵临圣城耶路撒冷。在城内驻扎的圣殿骑士团被歼灭之后,阿拉伯大军兵临城下。

来到山岗上,萨拉丁极目远眺,见两小儿辩日, 发现自己的军队排成了一个 NM 大小的方阵。作为一个的军事家,这种阵型是他非常抵触的。于是他打算将他的军队分割成1*1大小的小队。

然而拆分整编的军队是很费时间的一件事,对于行与行之间的每一条横线,列与列之间的每一条竖线,都有一个耗费时间的值(可以相同)。更加麻烦的是,拆分后的军队都是独立的,两个独立的部分不能拼接在一起拆分(比如一个显示器被拆成了两半,想把它拆成四份则需要对于已有的两份分别再拆一次) 。

现在萨拉丁知道每一条横线和每一条竖线耗费时间的值,但是他很忙,所以不能处理出将NM的方阵拆成1*1的小队的总消耗时间的最小值,于是他转向你帮忙。

输入
第一行包含两个数N与M,分别表示行和列的数量。

接下来N-1行,表示每一条横线的耗费时间值。

接下来M-1行,表示每一条竖线的耗费时间值。
1 <= N , M <= 2000

输出
一个数表示总耗费时间的最小值。

样例输入

2 2
3
3

样例输出

9

分析: 首先要想明白,行和行之间,列和列之间的消耗时间的值是互不影响的。只有行和列之间才会受到影响。每拆一个行,后续的列拆除就会多消耗一份时间。每拆一个列,后续的行拆除也会多消耗一份的时间。可以自己在纸上模拟一下。
贪心策略: 综上,我们应该尽可能的将耗费时间值大的行和列提前拆出来,所以分别将行列排序,每次取当前行列的最大值拆除。如果当前要拆除行,本次耗费的时间就是行的耗费值乘以之前列拆除的数量+1。如果当前要拆除列,本次耗费的时间就是列的耗费值乘以之前行拆除的数量+1。

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int row[2005],col[2005];
int main(){
    int n,m;scanf("%d %d",&n,&m);
    for(int i=1;i<n;i++)scanf("%d",&row[i]);
    for(int i=1;i<m;i++)scanf("%d",&col[i]);
    sort(row+1,row+n);reverse(row+1,row+n);
    sort(col+1,col+m);reverse(col+1,col+m);
    int i=1,j=1,sum=0,ro=1,co=1;
    while(i<n&&j<m){
        if(row[i]>col[j])sum+=row[i++]*co,ro++;
        else sum+=col[j++]*ro,co++;
    }
    while(i<n)sum+=row[i++]*co;
    while(j<m)sum+=col[j++]*ro;
    printf("%d\n",sum);
    return 0;
}

问题 G: Fy’s dota2

题目描述
Fy 觉得自己玩 cf,lol这种高端游戏已经够厉害了,于是他决定去玩dota2。结果 fy 的鼠标右键坏了,所以他就等到 2500 买了把闪烁匕首,用跳刀前进,准备去送泉水。但是 fy 一次最多前进 k 的距离,泉水离 fy 现在的距离是 n。

Fy 想知道他到泉水的方案数。

输入
第一行 2 个整数:k,n
1<=n<=2^31-1,1<=k<=10

输出
一行 1 个整数:代表答案对 7777777 取膜的结果

样例输入

2 4

样例输出

5

分析: 矩阵快速幂
容易发现,递推式为
在这里插入图片描述
因为n很大,暴力写肯定不行。所以有了我们的矩阵快速幂。可以在博客看看矩阵快速幂的原理和求法。能够解决什么样的问题。

在这里插入图片描述

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int n,k;
const int mod=7777777;
struct Martix
{
    ll a[15][15];
    Martix(){
        memset(a,0,sizeof a);
    }
    Martix operator *(const Martix x)const{//矩阵乘法
        Martix ans;
        for(int i=1;i<=k;++i)
            for(int j=1;j<=k;++j)
                for(int kk=1;kk<=k;++kk)
                    ans.a[i][j]=(ans.a[i][j]+a[i][kk]*x.a[kk][j])%mod;
        return ans;
    }
};
ll pow_(int x){
    Martix st,mul;
     for(int i=1;i<=k;++i){
        st.a[1][i]=pow(2,k-i);
        mul.a[i][1]=mul.a[i][i+1]=1;
    }
    while(x){
        if(x&1)st=st*mul;
        mul=mul*mul;
        x>>=1;
    }
    return st.a[1][1];
}
int main(){
    while(~scanf("%d %d",&k,&n)){
        if(k>=n)cout<<(int)pow(2,n-1)<<endl;
        else cout<<pow_(n-k)<<endl;
    }
}

问题 H: 计算购买兴趣相似度

题目描述
某电子商务系统采用如下算法来计算两个顾客的购买兴趣相似度(Jaccard相似度):
(1) 分别对两个顾客的商品购买集合进行处理,将同一个顾客购买的相同商品进行合并。本算法只需要考虑顾客购买了哪些商品,不需要知道某一种商品的购买数量。
(2) 对于两个顾客,合并各自相同物品后得到两个商品购买集合分别为A和B,则他们的购买兴趣相似度计算公式如下:
Sim(A, B) =|A∩B|/ |AUB|。
该相似度又称为Jaccard相似度,其值为A与B交集的大小与A与B并集的大小的比值。
现在给你两位顾客的原始商品购买集合(未去重),请编写一个程序计算他们的购买兴趣相似度。

输入
单组输入,输入两行。
第1行是第1位顾客的原始商品购买集合(未去重),第2行是第2位顾客的原始商品购买集合(未去重)。
两个商品之间用空格隔开。
原始商品购买集合中包含的商品数量不超过1000个。

输出
输出两位顾客的购买兴趣相似度,结果四舍五入保留两位小数。

样例输入

P1 P2 P3 P1 P4 P2
P1 P1 P4 P5

样例输出

0.40

分析: 用STL很好解决这道题,看看代码就明白了。不知道set,map的同学们可以去博客看看。

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
string s,t;
int main(){
    set<string>sum,ans;
    map<string,int>mp;
    int cnt=0;
    while(cin>>s){
        sum.insert(s),mp[s]=1;
        if(getchar()=='\n')break;
    }
    while(cin>>t){
        sum.insert(t);
        ans.insert(t);
        if(getchar()=='\n')break;
    }
    for(auto it=ans.begin();it!=ans.end();it++){
        if(mp[*it])cnt++;
    }
    printf("%.2f\n",cnt*1.0/sum.size());
    return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

a碟

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

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

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

打赏作者

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

抵扣说明:

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

余额充值