LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

P7777 『JROI-2』Shelter 题解

P7777 『JROI-2』Shelter 题解

题目链接:P7777 『JROI-2』Shelter

省流:二分求单谷函数的谷值

前记

蒟蒻的第二篇题解。

一般的二分和 算法其他人都已经说的很明白了,这里提供一种新的思路——不用特判,个人认为好理解,好写的二分。

读题

首先我们仔细的研究一下这两个规则。

  • 选择一个数 ,把第 堆石子抓取走,代价为
  • 选择两个数 ,把第 堆和第 堆石子抓走,代价为

我们发现,如果我们使用第二条规则,则一定是选取相邻的两个最优;同时还发现,我们一定是在前面一部分使用规则一,后面的使用规则二,因为你编号越小,使用规则一所花费的代价就越少,如果你编号大的都使用了规则二,则编号小的也一定要使用规则二。

于是乎,我们设 后 个使用规则二,则 前 个,使用规则一。

设花费总和为 则:

合并同类项即:

利用等差数列整理得:

我们这时可以感性理解一下,前一部分使用规则一,后一部分使用规则二,存在一个点到达最小值,那么 ,不就是个单谷函数么?

所以问题就转化成了求如下单谷函数的谷值。

inline int f(int i){return (n-2*i+1)*(n-2*i)/2*p+i*q;}//f(x)

二分

额,一般求单峰/谷函数我们最常用的是三分法,但对于这道题来说,反正我是没有用三分法写过去,原因可能是因为本人太弱了,一些毒瘤的问题我没有想到;同时 的时间复杂度(这里带上 2 的常数是为了体现出三分法要比二分慢一些)需要些卡常,不如使用二分(但二分也需要稍微卡一卡)。

在这道题中,是完完全全可以用二分求解单谷函数的谷值的,甚至于没有那些乱七八糟的特判,我们设一个点为 ,如果 ,则谷值点一定 ,同理,如果 ,则谷值点一定 ,如果 ,则 谷值点谷值。同时我们还要考虑不能出界,这个直接看代码吧。

while(l<r)
{
    mid=(l+r)>>1;
    if(mid-1>=0&&f(mid-1)<f(mid)) r=mid-1;//如果x可以更小,并且谷值点<x
    else if(mid+1<=n/2&&f(mid+1)<f(mid)) l=mid+1;//如果x可以更大,并且谷值点>x
    else l=r=mid;//若不行,则x为谷值点,直接将l和r设成 mid,下一步就直接跳出了
}
printf("%lld\n",f(l));

AC Code

代码挺短的,最后一个点基本可以稳定在 220ms 左右

#include<cstdio>
#define int long long
using namespace std;
int T,n,p,q;
inline int read()//快读是必要的
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
inline int f(int i){return (n-2*i+1)*(n-2*i)/2*p+i*q;}//f(x)
main()
{
    T=read();
    while(T--)
    {
        n=read(),p=read(),q=read();
        int l=0,r=n/2,mid;//注意边界
        while(l<r)
        {
            mid=(l+r)>>1;
            if(mid-1>=0&&f(mid-1)<f(mid)) r=mid-1;//如果x可以更小,并且谷值点<x
            else if(mid+1<=n/2&&f(mid+1)<f(mid)) l=mid+1;//如果x可以更大,并且谷值点>x
            else l=r=mid;//若不行,则x为谷值点,直接将l和r设成 mid,下一步就直接跳出了
        }
        printf("%lld\n",f(l));
    }
    return 0;
}

END

img_show