hdu 6748 Matrix 2020 百度之星 初赛一 T6 题解 二分法

传送门:HDU 6748 Matrix
前6题题解

Problem Description

有一个二维平面,给定 length[1],length[2],length[3],length[4],画出 4 个正方形区域。

第 i 个区域为 (x,y) | |x|<=length[i],|y|<=length[i]。

对于一个整点 (x,y) 其权值为 (|x|+|y|)∗cnt,其中 cnt
为覆盖该点的区域个数。(点在区域边界上或者在区域内,都称为被区域覆盖)。

现在需要在至少被一个区域覆盖的整点点集中,选出 k 个两两不同的点,使得总权值和最小。

输出最小的权值总和。

Input
第一行一个整数 T(T≤100) 表示 T 组测试数据。

每组数据有 5 个数字,分别表示 length[1],length[2],length[3],length[4],k。满足
1≤length[1]<length[2]<length[3]<length[4]≤ 1 0 9 , 0 ≤ k ≤ 1 0 10 10^9,0≤k≤10^{10} 109,0k1010

数据保证一定能选出 k 个点。

Output
对每组数据输出一行,表示答案。

说实话,费劲吧啦地把这题做出来之后还挺乐呵的,看到【博客园 渐近线i】的这篇题解后我哭了,辣么简单的问题被我搞得这么复杂(ㄒoㄒ)。
虽然那篇题解的时间复杂度是O(n)我的是O(log k).(耗时分别是:290多ms,0ms)
(n指最后一个被遍历的权值,大致应该是O( k \sqrt k k )的)
我把大致思路写一下吧,代码也写了不少注释。
思路
找到每个权值对应的小于这个权值的点的个数,权值和,二分找最接近的区间。权值小于区间左点的点的权值和,加上剩余的点数*区间右点,便是答案。

注意:
int最大是约是2e9,这题的k为1e10,所以要用long long。
二分区间不能太小否则可能找不到答案。
代码:

#include<iostream>
using namespace std;
typedef long long ll;
int length[]={
    
    0,1,2,3,4};
ll dots(int i,ll limit)//得到在第i个框内,第一象限内,x+y<limit的点的个数 
{
    
    
    if (limit==0)
    return 0;
    if (i==0)
    return 1;
    ll len=length[i];
    if (limit>=2*len) //把框全覆盖了
    return (len+1)*(len+1);
    ll n_i;
    if (limit<len) //limit在对角线下方
    {
    
    
        n_i=(1+limit+1)*(limit+1)/2;
    }
    else//limit在对角线上方
    {
    
    
        n_i=(len+1)*(len+1)-(1+(2*len-limit))*(2*len-limit)/2;
    }
    return n_i;

}
ll sec_dots(ll limit) //的到四个象限所有的满足:|x|+|y|<limit的点的个数
{
    
    
    ll r=0;
    for(int i=1;i<=4;i++)
    {
    
    
        ll redundant=limit/(5-i);
        if(redundant>length[i])
            redundant=length[i];
        ll v_red=(redundant-length[i-1]);//象限交界处的点的个数
        if (v_red<0)
        continue;
        r+=(dots(i,limit/(5-i))-dots(i-1,limit/(5-i))-v_red)*4;
        //cout<<i<<" "<<r<<endl;
    }
    return r;
}
ll value(int i,ll limit)//得到在第i个框内,第一象限内,x+y<limit的点的(x+y)的和
{
    
    
    ll v_i;
    ll len=length[i];
    if (i==0)
    return 0;
    if (limit>2*len) //把框全覆盖了
    limit=2*len;
    if (limit<len) //limit在对角线下方
    {
    
    
        v_i=limit*(limit+1)*(2*limit+1)/6+(1+limit)*limit/2;
    }
    else //limit在对象线上方
    {
    
    
        ll down=len*(len+1)*(2*len+1)/6+(1+len)*len/2;//对角线下方的和
        v_i=down+(len+1+limit)*(limit-len)*(2*len+1)/2-(limit*(limit+1)*(2*limit+1)-len*(len+1)*(2*len+1))/6;
    }
    
    return v_i;
}
ll sec_value(ll limit)//得到四个象限所有的满足:权值<limit的点的权值的和
{
    
    
    ll r=0;
    
    for(int i=1;i<=4;i++)
    {
    
    
        ll redundant=limit/(5-i);
        if(redundant>length[i])
        redundant=length[i];
        ll v_red=(redundant+length[i-1]+1)*(redundant-length[i-1])/2;//象限交界处的点的权值和
        if (v_red<0)
        continue;
        ll real=(value(i,limit/(5-i))-value(i-1,limit/(5-i))-v_red);
        //cout<<i<<"  "<<vuale(i,limit/(5-i))<<"  "<<vuale(i-1,limit/(5-i))<<"  "<<v_red<<"  "<<real<<endl;
        r+=real*(5-i)*4;
    }
    return r;
}

ll k;
ll l=0;
ll r;
ll dichotomy(ll left, ll right)//二分
{
    
    
    ll mid;
    k--;
    while (left < right)
    {
    
    
        mid = (left + right) / 2;
        ll ans = sec_dots(mid);
        if (ans < k)
            left = mid + 1;
        else if (ans > k)
        {
    
    
            right = mid - 1;
        }
        else
            return sec_value(mid);
    }
    //cout<<k<<"  "<<(k-sec_dots(left))<<"  "<<left<<" "<<right<<"  "<<sec_vuale(left)<<endl;
    while (sec_dots(left)<k) //找到最后一个少于k的
    {
    
    
        left++; 
    }  
    while (sec_dots(left)>k)
    {
    
    
        left--;  
    }
    while (sec_dots(right)>k) //找到第一个大于k的
    {
    
    
        right--; 
    }  
    while (sec_dots(right)<k)
    {
    
    
        right++; 
    }  
    return (k-sec_dots(left))*right+sec_value(left);

    
}
int test=1;
int main()
{
    
    
    cin>>test;
    for(int j=1; j<=test; j++)
    {
    
    
        for(int i=1;i<=4;i++)
        {
    
    
            scanf("%d",&length[i]);
        }
        scanf("%lld",&k);
        if (k<=1)
        printf("0\n");
        else
        {
    
    
            r=4e9;//这是权值上限
            printf("%lld\n",dichotomy(l,r));
        }
    }
    return 0;
}

做题记录:
这题恐怕是我提交次数最多的一题了(43次.。。。),后面的好多次都是因为tle,于是各种优化代码,缩小二分上限,但莫得用处。后来才发现是因为二分里有个while循环,如果找不到>k的就无限循环了,所以我越缩小上限越tle。而且,一个log的时间复杂度,怎么可能tle!还有没注意到k已经超int上限了,最后把好多变量由int改成了ll,就ac了,还挺惊喜的。看到别人的题解后我才知道我这不是什么好的解法。为什么会想到二分,我觉得跟我看到群里说这个题可以用二分去解有很大关系。。。anyway 主要还是因为缺乏经验吧

猜你喜欢

转载自blog.csdn.net/weixin_42378324/article/details/107568729