河南工大2024新生周赛(7)----命题人 刘义 题解

问题 A: 圆:

这是一个数学题,画图可得,4 个圆时,分割成 14 个区域,可以推导出结论:当圆为0个时,区域数为1个,当圆有x个的时候,区域数有x*x-x+2;

复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
signed main(){
    int a,b;//a为圆的个数,b为区域数
    cin>>n;
    while(n--)
    {
        cin>>a;
        if(a==0)
        b=1;
        else
        b=a*a-a+2;
        cout<<b<<" ";
    }
    return 0;
}
复制代码

问题 B: 旅人分宝:

这是一个经典的海盗分金问题,每个旅人只会选择一个对自己最有利的情况,并且要求50%以上的同意,所以当老大给一半的人每一个人一个金币,他们就会同意了。

复制代码
#include<bits/stdc++.h>
using namespace std;
int t;
int n,x;
int main()
{
    cin>>t;
    while(t--)
    {
        cin>>n>>x;
        if(x%2==0)
        {
            cout<<n-x/2+1<<endl;
        }
        else
        {
            cout<<n-x/2<<endl;
        }
    }
    return 0;
}
复制代码

 

问题 C: A*B:

经典的高精度乘法:

复制代码
#include <stdio.h>
#include <string.h>

//翻转函数
void reverse (int d[510], int len) {
    int temp, mid = len/2;
    for (int i = 0, j = len - 1; i < mid, j >= mid; i++, j--) {
        temp = d[i];
        d[i] = d[j];
        d[j] = temp;
    }
}

int main () {
    char s1[2005], s2[2005];
    int a[2005], b[2005], c[4010] = {0};
    scanf("%s", s1);
    scanf("%s", s2);
    int lena = strlen(s1);
    int lenb = strlen(s2);
    int lenc = lena + lenb;
    
    //将char的1转为int的1
    for (int i = 0; i < lena; i++) {
        a[i] = s1[i] - '0';
    }
    for (int i = 0; i < lenb; i++) {
        b[i] = s2[i] - '0';
    }
    
    reverse(a, lena);
    reverse(b, lenb);
    
    //核心代码
    for (int i = 0; i < lenb; i++) {
        for (int j = 0; j < lena; j++) {
            c[i+j] += b[i] * a[j];
            c[i+j+1] += c[i+j] / 10;
            c[i+j] = c[i+j] % 10;
        }
    }
    
    //处理前导0
    lenc = lenc - 1;
    while (c[lenc] == 0 && lenc >= 1) {
        lenc--;
    }
    
    //输出
    for (int i = lenc; i >= 0; i--) {
        printf("%d", c[i]);
    }
    
    return 0;
}
复制代码

问题 D: 时间加速器:

首先考虑暴力做法,即枚举最终答案ans,对于第一个可行的ans一定是最优解(在ans时间内可以完成ans+t(t>=0)的时间内也就一定可以完成)由于N<=500000的数据范围绝对会TLE,因此需要优化。

接下来考虑优化,由于之前提到的性质,设f(i)表示在i时间内有无可能完成,那么f数组必定满足单调性。那么我们可以二分最终答案ans,对于每一个ans对其进行暴力check:对于每一件任务,如果它能在ans天内完成(完成度<=ans*a),那么就不必使用时间加速器,接下来处理必须使用时间加速器的情况,当i号任务不能被直接完成时,我们需要用((x[i]-a*mid)%b==0?(x[i]-a*mid)/b:(x[i]-a*mid)/b+1;)次时间加速器进行完成,最后只需看时间加速器使用总数是否小于等于mid即可。

复制代码
#include<iostream>
#define int long long
using namespace std;
int n,a,b,x[600006];
bool check(int mid)//判断函数 
{
    int tot=0;
    for(int i=1;i<=n;i++)
    {
        if(x[i]<=mid*a)continue;
        else 
        {
            
            if((x[i]-mid*a)%b==0)tot+=(x[i]-mid*a)/b;
            else tot+=((x[i]-mid*a)/b+1);
        }
    }
    return tot<=mid;
}
signed main()
{
    cin>>n>>a>>b;
    for(int i=1;i<=n;i++)cin>>x[i];
    int l=0,r=1e9,mid;
    while(l<=r)//二分搜索 
    {
        mid=(l+r)/2;
        if(check(mid))r=mid-1;
        else l=mid+1;
    }
    cout<<l<<endl;
    return 0;
}
复制代码

问题 E: 切巧克力:

输入

4 2

5 6

1

输出

19

如果先切横的,结果就是1+2∗4+2∗5+2∗6=311+24+25+26=31。

如果先切竖的,结果就是4+5+6+1∗4=194+5+6+14=19。

显然先切竖的更优。

由此,可以得出一个结论:先切代价大的。

于是,思路就出来了。先把两个数组从大到小排序,然后两个数组挨个比较,较大的就在答案上加上这个代价(要乘上切了多少刀),再把指针往后挪一位,直到全部算完即可。

复制代码
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int n, m;
int a[10005], b[10005];//横线和竖线的代价

int main()
{
    cin >> n >> m;
    int s1 = 1, s2 = 1;//s1和s2可以是a数组和b数组的指针,也可以是横纵分成的块数
    for (int i = 1; i <= n-1; i++)
    {
        cin >> a[i];
    }
    for (int i = 1; i <= m - 1; i++)
    {
        cin >> b[i];
    }
    for(int i=1;i<=n-1;i++)
    {
        for(int j=1;j<=n-1-i;j++)
        {
            if(a[j]<a[j+1])
            {
                int t;
                t=a[j];
                a[j]=a[j+1];
                a[j+1]=t;
            }
        }
    }
    for(int i=1;i<=m-1;i++)
    {
        for(int j=1;j<=m-1-i;j++)
        {
            if(b[j]<b[j+1])
            {
                int t;
                t=b[j];
                b[j]=b[j+1];
                b[j+1]=t;
            }
        }
    }
    long long ans = 0;
    for (int i = 2; i < n + m; i++)
    {
        if (a[s1] > b[s2])
            ans += s2 * a[s1++];//选择大的,这里s2表示块数,s1指针后移一位
        else
            ans += s1 * b[s2++];//同理,和上面相反
    }
    cout << ans << endl;
    return 0;
}
复制代码

问题 F: 建房子:

构建一个01背包。

思路是将体力看做背包 将石子看做物品 然后就很自然地得出了本题的状态转移方程:

f[j]=max(f[j],f[j-w[i]]+v[i])

复制代码
#include<stdio.h>
 #include<bits/stdc++.h>
 using namespace std;
 int n,V,c;
 int v[100001],l[100001];//v:石头体积 l;所需的体力 
 int dp[100001];
 int main()
 {
     scanf("%d %d %d",&V,&n,&c);
     int sum=0;
     for(int i=1;i<=n;i++)
     {
         scanf("%d %d",&v[i],&l[i]);
         sum+=v[i];
     }
     if(sum<V)//若石子总体积<V 
     {
         printf("Impossible");
         return 0;
     }
     for(int i=1;i<=n;i++)
     {
         for(int j=c;j>=l[i];j--)
         {
             dp[j]=max(dp[j],dp[j-l[i]]+v[i]);
         }
     }
     for(int i=0;i<=V;i++)//从小到大搜 第一个大于V的直接输出
     {
         if(dp[i]>=V)
         {
             printf("%d",c);
             return 0;
         }
     }
     printf("Impossible");
     return 0;
 }
复制代码

 

问题 G: 命运之桥:

此题可以用排序做(高档一点的模拟)

核心思想:两个人相遇转身,相当于交换灵魂后继续走

最大值:最靠近端点两个人各自向对方走,时间较长的那个人的时间

最小值:所有人中走完桥最小值中的最大值

复制代码
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int l, a[100001];
int main()
{
    int n;
    scanf("%d",&l);
    scanf("%d",&n);
    if (!n)
    {
        printf("0 0\n");
        return 0;
    }
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
    }
    sort(a + 1, a + 1 + n); //从小到大排序(算最长时间时可能方便一些)
    int max_time, min_time;
    for (int i = 1; i <= n; i++)
    {
        min_time = max(min(a[i], l + 1 - a[i]), min_time);//最短时间就是所有人中走完桥最小值中的最大值 
    }
    max_time = max(l + 1 - a[1], a[n]);//最长时间就是最靠近端点两个人各自向对方走,
    printf("%d %d", min_time, max_time);
    return 0;
}
复制代码

问题 H: 恶魔:

一个恶魔能杀掉的最大个数应该为两侧小于它血量的第一个的恶魔能杀掉的个数 + 1,先固定左侧,用单调栈维护一下该侧比当前恶魔更小的第一个位置,右侧同理

复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n;
int a[N],l[N],r[N];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    stack<int>s;
    for(int i=1;i<=n;i++)//左边
    {
        while(!s.empty()&&a[s.top()]>a[i]) s.pop();
        if(!s.empty()) l[i]=l[s.top()]+1;
        else l[i]=0;
        s.push(i);
    }
    reverse(a+1,a+1+n);//反转一次,开始做右边
    stack<int>s1;
    for(int i=1;i<=n;i++)//右边
    {
        while(!s1.empty()&&a[s1.top()]>a[i]) s1.pop();
        if(!s1.empty()) r[i]=r[s1.top()]+1;
        else r[i]=0;
        s1.push(i);
    }
    for(int i=1;i<=n;i++)
    {
        cout<<l[i]+r[n-i+1]<<" ";
    }
    return 0;
}
复制代码

 

posted @   河南工业大学算法协会  阅读(80)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 2025年广告第一单,试试这款永久免费的开源BI工具
· o3 发布了,摔碎了码农的饭碗
· [.NET] API网关选择:YARP还是Ocelot?
· 用 2025 年的工具,秒杀了 2022 年的题目。
· 为什么 .NET8线程池 容易引发线程饥饿
点击右上角即可分享
微信分享提示