暑假的第一次测试(三)

E题 Arthur and Table(原题codeforces 557 C)

大致题意:有一张桌子,有n个桌脚,有长有短,现在要砍掉部分桌脚,使桌子保持平衡。砍桌脚需要花费一定的精力,最后留下的桌脚要有一半以上相同且是最大长度的,求最小要花费的精力。

注:只剩一个桌脚算平衡,只剩两个桌脚必须一样长。

我的想法是先求砍掉所有桌脚所需的精力s,然后将桌子的桌脚按长度进行排序,小的在前面。然后依次计算,寻找一样的长度的桌脚,如果只有1个,那只能留下这一个;如果有t个,则可以另外找t-1个比它短的桌脚,也就是说找它之前的桌脚,要t-1个最大的。最后再(s - 留下桌脚的精力)中求个最小值就行了。程序中求t-1个比当前桌脚短的最长的桌脚的时候用了下优先队列,方便找,虽然不知道会不会节约时间0.0

#include<iostream>
#include<queue>
using namespace std;
const int MAXN=100012;
int n,i,sum,num,j,s,minn,k,ii;
int a[MAXN],b[MAXN],c[MAXN];
priority_queue<int> Q;
int min(int a,int b)
{
    if (a<b) return a; else return b;
}
void kp(int x,int y)
{
    int i,j,mid,temp;
    i=x;j=y; mid=a[(i+j)/2];
    while (i<j)
    {
        while (a[i]<mid) i++;
        while (a[j]>mid) j--;
        if (i<=j)
        {
            temp=a[i]; a[i]=a[j]; a[j]=temp;
            temp=b[i]; b[i]=b[j]; b[j]=temp;
            i++; j--;
        }
    }
    if (x<j) kp(x,j);
    if (i<y) kp(i,y);
}
int main()
{
    sum=0;
    cin>>n;
    for (i=1;i<=n;i++) cin>>a[i];
    for (i=1;i<=n;i++)
    {
        cin>>b[i];
        sum+=b[i];//求总消耗的体力
    }
    kp(1,n);//对桌脚按长度从小到大排序
    i=1; minn=sum;
    while (i<=n)
    {
        num=1;
        s=b[i]; //s用于记录留下的桌脚若要砍掉所需的体力
        ii=i;
        while (i<n&&a[i+1]==a[i])
        {
            num++;
            i++;
            s+=b[i];
        }
        if (num==1) minn=min(minn,sum-s);
        else
        {
            k=0;
            while (!Q.empty())//从队列中取出num-1个消耗体力最大的
            {
                s+=Q.top();
                k++;
                c[k]=Q.top();
                Q.pop();
                if (k==num-1) break;
            }
            minn=min(minn,sum-s);
            for (j=1;j<=k;j++)//拿出来的还得放回去0.0
            Q.push(c[j]);
        }
        if (minn==0) break;//如果不用花费体力就直接结束
        for (j=ii;j<=i;j++) Q.push(b[j]);//把当前长度的都放入队列中
        i++;
    }
    cout<<minn<<endl;
    return 0;
}

F题  Vitaly and Cycle(原题codeforces 557 D)

大致题意:当前有n个点,m条边,要添加最少的边,至少构成一个奇数条边的环,并且计算有多少种可能。

最小的奇数环是3,如果已有奇数环,就不加边;若一条边都没,那最少就得加3条边;若边都不相连,就要加2条边;加1条边的情况最复杂,下次补上

分四种情况讨论:

1、加0条边   1种可能

2、加3条边    C(n,3)种可能

3、加2条边   m*(n-2)种可能

4、加1条边    我还没想好,想好了再补上- -

       

G题 Drainage Ditches

最基础的最大流,1为源点,n为汇点,求最大流(代码详见之前的blog)


H题 GukiZ and Contest(原题codeforces 307 A)

一个快排,再加上些计算排名的东东,解决问题。

#include<iostream>
using namespace std;
int n,i;
int a[2012],b[2012],p[2012];
void kp(int x,int y)
{
    int i,j,mid,temp;
    i=x;j=y; mid=a[(i+j)/2];
    while (i<j)
    {
        while (a[i]>mid) i++;
        while (a[j]<mid) j--;
        if (i<=j)
        {
            temp=a[i]; a[i]=a[j]; a[j]=temp;
            temp=b[i]; b[i]=b[j]; b[j]=temp;
            i++; j--;
        }
    }
    if (x<j) kp(x,j);
    if (i<y) kp(i,y);
}
int main()
{
    cin>>n;
    for (i=1;i<=n;i++)
    {
        cin>>a[i];
        b[i]=i;
    }
    kp(1,n);
    p[b[1]]=1;
    for (i=2;i<=n;i++)
        if (a[i]==a[i-1]) p[b[i]]=p[b[i-1]];
        else p[b[i]]=i;
    for (i=1;i<=n-1;i++) cout<<p[i]<<' ';
    cout<<p[n]<<endl;
    return 0;
}


<think> 根据用户描述,这是一个关于最小公倍数(LCM)的问题。问题要求:除第一天外,所有学生再次同一天上课的最少天数。实际上,这相当于求所有学生上课频率的最小公倍数(因为每个学生的上课周期是固定的,他们再次同一天上课的周期就是这些频率的最小公倍数)。 注意:题目要求“除第一天外”,所以第一天是初始上课日,然后我们需要计算下一次所有学生再次同一天上课是在多少天之后。 例如:有3个学生,上课频率分别为2,3,4(即每隔2天、3天、4天上课一次)。那么他们下一次同时上课的日子是第12天(因为2,3,4的最小公倍数是12),所以除第一天外,最少天数为12(也就是第12天再次一起上课,距离第一天有11天,但题目要求的是“天数”指的是从第一天后的第几天?这里需要明确)。 但是,题目要求的是“除第一天外所有学生再次同一天上课的最少天数”,也就是说,从第一天后开始算起,到下一次所有学生同一天上课,这个间隔的天数。实际上,这个间隔的天数就是所有频率的最小公倍数。因为: - 第一个学生:在第一天,第一天+2,第一天+4,... - 第二个学生:在第一天,第一天+3,第一天+6,... - 第个学生:在第一天,第一天+4,第一天+8,... 他们下一次同时上课是在第一天加上最小公倍数的那天。所以间隔的天数就是最小公倍数。 然而,题目说“除第一天外”,所以这个天数就是最小公倍数,因为第一天是初始日,然后我们要求的是从初始日之后,下一次共同上课的日子在初始日后的多少天。 因此,问题转化为:给定n个正整数(每个代表一个学生的上课频率),求这n个数的最小公倍数。 注意:最小公倍数的计算可以通过最大公约数(GCD)来求解。公式为:LCM(a,b) = a*b / GCD(a,b)。多个数的最小公倍数可以通过迭代求解:先求前两个数的LCM,再用这个LCM和下一个数求LCM,以此类推。 但是,这里有一个问题:如果频率的值比较大,而且数量比较多,那么连续计算可能会导致中间结果溢出。因此,我们可以先除后乘:LCM(a,b) = a / GCD(a,b) * b (这样可以在一定程度上避免溢出)。 步骤: 1. 读入学生数量n。 2. 读入n个学生的上课频率,存储在数组中。 3. 计算这n个数的最小公倍数(迭代计算)。 4. 输出这个最小公倍数(即为所求的最少天数)。 注意:题目要求是C++程序。 下面我们编写代码: 首先,实现求两个数的最大公约数的函数(使用欧几里得算法)。 然后,利用这个函数求两个数的最小公倍数。 最后,迭代求整个数组的最小公倍数。 注意:如果输入中有0,题目没有明确说明,但频率应该是正整数(因为0表示不上课,不合理),所以我们假设输入的都是正整数(至少为1)。 代码结构: 1. 包含头文件(iostream, vector等) 2. 定义求最大公约数的函数gcd(a, b) 3. 定义求最小公倍数的函数lcm(a, b) 4. 主函数: 输入n 输入n个频率,存放在vector中 初始化result = 1 遍历vector,用result和每个频率求lcm,更新result 输出result 注意:如果输入只有一个学生,那么最小公倍数就是他的频率。 让我们写代码: </think> 根据您的需求,我将编写一个C++程序解决暑假兴趣班排班问题。核心思路是计算所有学生上课频率的最小公倍数(LCM),因为所有学生再次同一天上课的周期就是频率的最小公倍数。 ### C++解决方案 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; // 计算最大公约数 (GCD) long long gcd(long long a, long long b) { while (b != 0) { long long temp = b; b = a % b; a = temp; } return a; } // 计算最小公倍数 (LCM) long long lcm(long long a, long long b) { return (a / gcd(a, b)) * b; // 先除后乘避免溢出 } int main() { int n; cout << "输入学生数量: "; cin >> n; vector<long long> frequencies(n); cout << "输入每位学生的上课频率(用空格分隔):\n"; for (int i = 0; i < n; i++) { cin >> frequencies[i]; } // 计算所有频率的最小公倍数 long long result = frequencies[0]; for (int i = 1; i < n; i++) { result = lcm(result, frequencies[i]); } cout << "所有学生再次同一天上课的最少天数: " << result << endl; return 0; } ``` ### 算法说明 1. **问题转化**:相当于求所有频率的最小公倍数 $LCM(f_1, f_2, ..., f_n)$ 2. **数学原理**: - 最大公约数(GCD):$gcd(a,b)$ 使用欧几里得算法 - 最小公倍数(LCM):$lcm(a,b) = \frac{|a \times b|}{gcd(a,b)}$ - 多个数的LCM:迭代计算 $lcm(a,b,c) = lcm(lcm(a,b),c)$ 3. **防溢出处理**:`(a / gcd(a,b)) * b` 避免大数相乘溢出 4. **时间复杂度**:$O(n \log(max⁡(f)))$,高效处理大输入量 ### 示例运行 ``` 输入学生数量: 3 输入每位学生的上课频率(用空格分隔): 2 3 4 所有学生再次同一天上课的最少天数: 12 ``` 解释:学生上课周期为每2天、3天、4天,下一次共同上课日是第12天(最小公倍数)[^1]。 ### 相关问题 1. 如果某些学生的上课周期有倍数关系,算法如何优化? 2. 如何处理非整数周期(如每2.5天上课)的情况? 3. 如果学生数量极大(如10⁶),如何进一步优化算法效率? 4. 除最小公倍数外,是否有其他因素影响实际排班安排? [^1]: 最小公倍数计算原理参考数论基础 [^2]: 循环结构处理参考学生成绩排序实现 [^3]: 输入处理技巧参考日期计算实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值