1 条题解

  • 0
    @ 2025-8-24 21:26:36

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Hexarhy
    干好饭,睡好觉,遇见好人。

    搬运于2025-08-24 21:26:35,当前版本为作者最后更新于2019-08-07 00:28:55,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    好久没有写二分答案了,来复习复习。顺便补充个像样的有LaTeX\LaTeX的题解。


    刚开始看到题目还以为是01背包,但却是求平均值最大,故不考虑。

    但是不好思考呢……那先从式子入手:

    假设存在一种最优解ansans,则

    $$\large ans=\frac{\sum^m_{i=1}v_i}{\sum^m_{i=1}c_i} $$

    式子太复杂?尝试化一下。

    去分母得,i=1mcians=i=1mvi\text{去分母得,}\sum^m_{i=1}c_i·ans=\sum^m_{i=1}v_i 移项得,i=1mciansi=1mvi=0\text{移项得,}\sum^m_{i=1}c_i·ans-\sum^m_{i=1}v_i=0

    观察式子,不难发现最优解ansans会使得左边=0=0,因此我们的目标就是尽可能地的靠近00。加上ansans具有单调性,这时就考虑到二分。

    check()也就很好写了。由于我们想让左边尽可能<0<0,则考虑贪心,排序并选前mm个最小的权值并得到之和tottot

    此时要对check()得到的tottot进行分类讨论了:

    • tot<0tot<0时,说明ansans还能再大一点,故二分时l=midl=mid,往右靠。

    • tot>0tot>0时,说明ansans还能再小一点,故二分时r=midr=mid,往左靠。

    • tot=0tot=0时,就是答案了,可以直接结束二分。

    另外,由于有小数,二分结束条件l,rl,r之间就不是rl1r-l\ge 1,而是形如rl105r-l\le 10^{-5}保证精度。


    时间复杂度:

    • 输入:Θ(n)\Theta(n)

    • 二分:Θ(logmax{vivi})\Theta(\log\max\large\{\frac{v_i}{v_i}\})

    • 排序:Θ(nlogn)\Theta(n\log n)

    • 计数tottotΘ(m)\Theta(m)

    综上所述,时间复杂度为:

    $$\large\Theta(n+(m+n\log n)\log\max\{\frac{v_i}{v_i}\}) $$$$\large\approx\Theta(n\log n \log \max\{\frac{v_i}{v_i}\}) $$

    最坏也不过达到大约2×1042\times 10^4


    讲得很细了,代码注释就不给那么多了。分析里都有。

    代码如下:

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    
    const int MAXN=500;
    int n,m;
    struct coffee
    {
    	int v,t;
    	double avr;//算权值t*x-v
    	bool operator<(const coffee a)const
    	{
    		return avr<a.avr;
    	}
    }a[MAXN];
    double ans,l,r;
    
    
    void input(void)
    {
    	cin>>n>>m;
    	for(int i=1;i<=n;i++)
    	 cin>>a[i].v;
    	for(int i=1;i<=n;i++)
     	 cin>>a[i].t;
    }
    
    bool check(const double x)
    {
    	for(int i=1;i<=n;i++)
    	 a[i].avr=x*a[i].t-a[i].v;//算每个的权值
        sort(a+1,a+n+1);//从小到大排序
    	double tot=0;
    	for(int i=1;i<=m;i++)//取前m个小的
    	 tot+=a[i].avr;
    	return tot<=0;
    }
    
    void binary(void)
    {
    	for(int i=1;i<=n;i++)
    	 if(a[i].v*1.0/a[i].t>r)
    	  r=a[i].v*1.0/a[i].t;//算出上界
    	while(r-l>1e-5)
    	{
    		const double mid=(l+r)/2.0;
    		if(check(mid))//注意分类讨论(这里结合二分求上下界)
    		 l=mid;
    		else
    		 r=mid;
    	}
    	ans=l;
    }
    
    int main()
    {
    	input();
    	binary();
    	printf("%.3f\n",ans);
    	return 0;
    }
    

    还没结束呢!

    现在仔细想想,或许可以不用二分(优势在于避免分类讨论和上下界问题),考虑枚举x(x[0,max{vici}])x(x\in[0,\max\large\{\frac{v_i}{c_i}\}]),并进行check(),好像也不会超时。理论时间复杂度:

    Θ(nlognmax{vici})\Theta(n\log n \max\{\frac{v_i}{c_i}\})

    最坏情况大约为1.5×1081.5\times 10^8

    但实际题目中除非专门卡否则一般达不到这样的复杂度,加上编译器的优化和lg评测机的良好性能,卡一卡(甚至不用)就能过去。

    当然以上只是个人看法,作者还没实践过。


    完结撒花!✿✿ヽ(°▽°)ノ✿

    • 1

    信息

    ID
    563
    时间
    1000ms
    内存
    125MiB
    难度
    3
    标签
    递交数
    0
    已通过
    0
    上传者