题目描述
一个阳光明媚的周末,二哥出去游山玩水,然而粗心的二哥在路上把钱包弄丢了。傍晚时分二哥来到了一家小旅店,他翻便全身的口袋也没翻着多少钱,而他身上唯一值钱的就是一条漂亮的金链。这条金链散发着奇异的光泽,据说戴上它能保佑考试门门不挂,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%的数据,;
对70%的数据,;
对100%的数据,,
。
数据规模较大,建议用scanf("%d", &a[i]);来读数据。
题目吐槽
这个链子不错,我也想要,RP++,考试门门不挂,RP++
题目解答
有一说一真的不难,不过按照惯例还是先来一波最笨的代码吧,一一列举,然后用长度为2的滑动窗口(黄色)来解决问题
应为是环状的所以要补充一下下在最后补充开始的数值,实际操作中取模就可以啦
以题目中例1解释,滑动窗口的长度为2,滑动如下
1 | 2 | 3 | 4 | 5 | 1 |
1 | 2 | 3 | 4 | 5 | 1 |
1 | 2 | 3 | 4 | 5 | 1 |
1 | 2 | 3 | 4 | 5 | 1 |
1 | 2 | 3 | 4 | 5 | 1 |
每次找到滑动窗口的里面的最大值,用蓝色标记,然后比较这些蓝色的数字
废话不说,上代码:
#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;
}
后来我找到了更快的方法,上面那个方法时间复杂度为,
hhhh,显然没有那么容易过关
优化算法,从偷懒开始,不妨假设老板要五条hhh
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ………… | n |
组内max |
下一次查找按照之前的就是从2开始,但是这是没有必要的(理由如下分析)我们也可以直接从5开始啦
理由:首先,我们的目标是找到最优解,并且最快,下面这三种情况明显不可能找到更优的解法,只可能找到更亏的解法。
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ………… | n |
组内max |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ………… | n |
组内max |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ………… | n |
组内max |
所以,最机智的方法是:哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈,按照下面开始查找
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ………… | 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个照搬前面的赋值就可以
1 | 2 | 3 | …… | n-1 | n | 1 | 2 | 3 | …… | n-1 | n |
![]() | ![]() | ![]() | ![]() |
一旦滑块的右端点越界,stop 停止!不用找了一定可以得出结果的
要不要写代码呢有点小偷懒了
还是写了,而且意外的上了排行榜第一hhh
评测状态 | 运行时间 | 内存 | 分数 | 语言 | 提交时间 |
---|---|---|---|---|---|
Accepted | 186ms | 23356KiB | 100 | C++ | 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;
}
平安结束,好耶