(新版)SJTU-OJ-1007. 二哥的金链

题目描述

一个阳光明媚的周末,二哥出去游山玩水,然而粗心的二哥在路上把钱包弄丢了。傍晚时分二哥来到了一家小旅店,他翻便全身的口袋也没翻着多少钱,而他身上唯一值钱的就是一条漂亮的金链。这条金链散发着奇异的光泽,据说戴上它能保佑考试门门不挂,RP++。好心的老板很同情二哥的遭遇,同意二哥用这条金链来结帐。虽然二哥很舍不得这条金链,但是他必须用它来付一晚上的房钱了。

金链是环状的,一共有 N 节,老板的要价是 K 节。随便取下其中 K 节自然没问题,然而金链上每一节的 RP 值其实并不一样,有高有低,二哥自己非常清楚。另外二哥并不希望把整个金链都拆散了,他只愿意在这条环形的金链上切两刀,从而切下一段恰好为 K 节的金链给老板。因为 RP 值越高的节越稀有,因此他希望给老板的金链上最高的 RP 值最小。

输入格式

第一行两个整数 N 和 K,表示金项链有 N 节,老板要价 K 节。

第二行用空格隔开的N个正整数 a1...aN ,表示每一节金链的价值为多少。

输出格式

输出一个整数,表示二哥给老板的金链上最高的 RP 值最小多少。

样例输入

样例输入 1

5 2
1 2 3 4 5

样例输入 2

6 3
1 4 7 2 8 3

样例输出

样例输出 1

2

样例输出 2

4

数据范围

对40%的数据,3 \leq N \leq 200

对70%的数据,3 \leq N \leq 20000

对100%的数据,3 \leq N \leq 200000, 0 < ai \leq 10^9

数据规模较大,建议用scanf("%d", &a[i]);来读数据。

题目吐槽

        这个链子不错,我也想要,RP++,考试门门不挂,RP++

题目解答

        有一说一真的不难,不过按照惯例还是先来一波最笨的代码吧,一一列举,然后用长度为2的滑动窗口(黄色)来解决问题

        应为是环状的所以要补充一下下在最后补充开始的数值,实际操作中取模就可以啦

        以题目中例1解释,滑动窗口的长度为2,滑动如下

123451
123451
123451
123451
123451

         每次找到滑动窗口的里面的最大值,用蓝色标记,然后比较这些蓝色的数字

        废话不说,上代码:

#include <iostream>
using namespace std;

long long int a[200010];
long int n,k;

long long int findmax(int l, int num)//找到一部分里面最大值,l是寻找的起点 
                                     //num是滑动方块大小
{
    long long int temp=a[l];
    for (long int i = l + 1; i < l + num; i++)
    {
        if (a[i % n] > temp)         //防止越界的最好方法就是取模啦
            temp = a[i % n];
    }
    return temp;
}

int main()
{
    long long int ans;
    cin >> n >> k;
    for (long int i = 0; i < n; i++)
    {
        scanf("%d", &a[i]);
    }
    ans = findmax(0, k);
    for (int i = 1; i < n; i++)
    {
        if (findmax(i, k) < ans)
            ans = findmax(i, k);      //遍历一遍,遇到更优解就更新,否则pass
        // cout << findmax(i, k) << endl;
    }                        
    cout << ans;
    system("pause");
    return 0;
}

        后来我找到了更快的方法,上面那个方法时间复杂度为O(n \times k),

        hhhh,显然没有那么容易过关

        优化算法,从偷懒开始,不妨假设老板要五条hhh

12345678910…………n
组内max

        下一次查找按照之前的就是从2开始,但是这是没有必要的(理由如下分析)我们也可以直接从5开始啦

        理由:首先,我们的目标是找到最优解,并且最快,下面这三种情况明显不可能找到更优的解法,只可能找到更亏的解法。

12345678910…………n
组内max
12345678910…………n
组内max

12345678910…………n
组内max

        所以,最机智的方法是:哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈,按照下面开始查找

12345678910…………n
组内max

        能省步骤就多省一点点吧hhh不香嘛

        然后就做出了一个微妙的修改,定义了一个全局变量tag,在找到最大值的时候把这个最大值对应的序号传入进去tag,在进行下一次查找的时候直接从tag+1的位置开始。

        其实最后还写了一段我个人觉得很没有素质的代码,就是我也不知道该怎么让这个do while循环退出,毕竟这个滑动窗口可能滑动多少次我也不知道,所以怎么办呢,我觉得第二次轮回的时候emmm一定会找到之前的那一次,举个例子嘛(你就想象一串佛珠就可以啦)第二回轮回一定可以滑到之前的那一次最优解,那就好说了,一旦滑动窗口的返回的函数值与之前的已经查找过的最优解相同,就是程序重复啦,那不就是该退出了吗?

        看似正确其实不对

        万一第一次滑动窗口返回的是9,第二回返回的是8,第三回返回的是8,第四回返回的是6,之后滑动窗口才会重复回到起点呢?

        明显有bug啊(但是测评机还是给我过了,今天总结的时候才发现)

        是的,然后我改进了,还以意外的缩短了时间hhh我好机智

#include <iostream>
using namespace std;

long long int a[200010];
long int n,k,tag;

long long int findmax(int l, int num)//找到一部分里面的最大值
{
    long long int temp=a[l];
    for (long int i = l + 1; i < l + num; i++)
    {
        if (a[i % n] > temp)
        {
            temp = a[i % n];
            tag = i % n;
        }    
    }
    return temp;
}

int main()
{
    long long int ans;
    cin >> n >> k;
    for (long int i = 0; i < n; i++)
    {
        scanf("%d", &a[i]);
    }
    ans = findmax(0, k);
    int i = 1;
    do
    {
        if (findmax(i, k) < ans)
            ans = findmax(i, k);
        i = tag + 1;
        if (findmax(i, k) == ans)
        {
            break;
        }
    } while (1);
    cout << ans;
    system("pause");
    return 0;
}

        所以做一个小改进吧,也算是更加严谨咯

        想了很多方法,最后个人觉得最优解法还是emmm准备好两个数组块,后面的那n个照搬前面的赋值就可以

123……n-1n123……n-1n
a_1a_na_1a_n

        一旦滑块的右端点越界,stop 停止!不用找了一定可以得出结果的

        要不要写代码呢有点小偷懒了

        还是写了,而且意外的上了排行榜第一hhh

评测状态运行时间内存分数语言提交时间
Accepted186ms23356KiB100C++Jul-12-2021 19:41:00

        没有做什么特别的改进,唯一就是把输入的部分增加了一个赋值,应为数组长度变长了成了2n,终止条件也修改成了(i + k - 1 > 2 * n - 1)

        当然,前提还有一个,findmax函数里面的取模符号不用了,这波不亏啊哈哈哈赚了

#include <iostream>
using namespace std;

long long int a[400010];
long int n,k,tag;

long long int findmax(int l, int num)//找到一部分里面的最大值
{
    long long int temp=a[l];
    for (long int i = l + 1; i < l + num; i++)
    {
        if (a[i] > temp)
        {
            temp = a[i];
            tag = i;
        }    
    }
    return temp;
}

int main()
{
    long long int ans;
    cin >> n >> k;
    for (long int i = 0; i < n; i++)
    {
        scanf("%d", &a[i]);
        a[n + i] = a[i];
    }
    ans = findmax(0, k);
    int i = 1;
    do
    {
        if (findmax(i, k) < ans)
            ans = findmax(i, k);
        i = tag + 1;
        if (i + k - 1 > 2 * n - 1)
        {
            break;
        }
    } while (1);
    cout << ans;
    system("pause");
    return 0;
}

        平安结束,好耶

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值