前言
前置知识
有手就行
题目描述
题目大意
Sisyphus 在一个圆形的房间里,房间内有 n n n 扇锁着的门,他有 n n n 把钥匙,其中第 i i i 把钥匙对应第 v i v_i vi 扇门,遇到不匹配的钥匙就放到钥匙扣的另一边。他需要解锁所有的门。Sisyphus 沿着门编号顺序,每次到一扇门前就找到第 i i i 把钥匙尝试打开门,直到找到正确的钥匙,然后把门再锁上,一直到他第 k k k 次成功解锁为止。
数据范围
- 对于 40 % 40\% 40% 的数据,保证 1 l e q n , k ≤ 1000 , 1 ≤ n , k ≤ 1000 1 leq n,k\leq 1000,1 \leq n,k \leq 1000 1leqn,k≤1000,1≤n,k≤1000。
- 对于 60 % 60\% 60% 的数据,保证 1 1 1 ≤ k ≤ 5 × 1 0 4 , 1 ≤ k ≤ 5 × 1 0 4 \leq k \leq 5 \times 10^4,1 \leq k \leq 5 \times 10^4 ≤k≤5×104,1≤k≤5×104。
- 对于所有数据, 1 ≤ v i ≤ n ≤ 1 0 5 , 1 ⩽ v i ≤ n ≤ 1 0 5 , 1 ≤ k ≤ 1 0 9 1\leq v_i\leq n\leq 10^5,1⩽v_i \leq n \leq 10^5,1 \leq k \leq 10^9 1≤vi≤n≤105,1⩽vi≤n≤105,1≤k≤109。
思路
初步思路(24pts)
看到此题,第一反应肯定是暴力,模拟到每一扇门尝试每一把钥匙,这样的时间复杂度为 O ( n k ) O(nk) O(nk),肯定过不了,提交只有 24 p t s 24pts 24pts。
优化(48pts)
我们需要注意的是,当面对第 i i i 扇门,用最右边的钥匙解锁第 i − 1 i - 1 i−1 扇门,并且在用循环变量 i i i 和 j j j 标记的钥匙之间有恒定数量的钥匙(即差一定)。
这样,找出 S i s y p h u s Sisyphus Sisyphus 插错了多少把钥匙的时间复杂度就变成了 O ( 1 ) O(1) O(1),于是我们得到了时间复杂度为 O ( k ) O(k) O(k) 的解,结合数据范围可得,可以拿到 60 % 60\% 60% 的分数。
//48分比较简单,就不加注释了
#include<bits/stdc++.h>
using namespace std;
int v[1000001];
int n,k,ans;
int p=1;
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
v[x]=i;
//为了方便处理,把输入的 $v_i$ 改成了表示第 $i$ 扇门的钥匙编号
}
for(int i=1;i<=k;i++)
{
if(p>n)p=(p-1)%n+1;
int t=(i-1)%n+1;
if(v[t]>=p)
{
ans+=v[t]-p;
p+=v[t]-p;
}
else
{
ans+=v[t]+n-p;
p+=v[t]+n-p;
}
}
cout<<ans;
return 0;
}
正解(80pts)
我们不难发现,对于尝试每一圈的 n n n 扇门,其实这是一个循环的过程,所以我们只需要遍历一圈即可。
我们可以用 a i a_i ai 表示从第 1 1 1 扇门到第 i − 1 i-1 i−1 扇门尝试的次数,然后我们维护 p p p 表示当前已经选到的钥匙编号,然后从 2 2 2 到 n + 1 n+1 n+1 遍历,对于每一扇门,更新 p p p 和 a i a_i ai 的值,最后可以计算出试完第 k k k 扇门的尝试次数,即:
a [ n ] × ( k ÷ n ) + a [ k m o d n ] a[n]\times(k\div n)+a[k\bmod n] a[n]×(k÷n)+a[kmodn]
这是什么意思?很好理解,我们已经计算出了每圈的尝试次数,我们用次数乘上圈数就是整圈的尝试次数,然后我们再加上剩余不足一圈的尝试次数,就是答案了。
Q:为什么是从 2 2 2 到 n + 1 n+1 n+1 遍历?
A:因为 a i a_i ai 表示的是从第 1 1 1 扇门到第 i − 1 i-1 i−1 扇门尝试的次数。对于第 i − 1 i-1 i−1 扇门,从第 1 1 1 扇门到这扇门的尝试次数等于上一次的尝试次数加上这一次的尝试次数。
注意两个坑
-
下一扇门的钥匙编号可能到下一圈,更新时要判断。
-
k k k 一定要减一,因为有 k m o d n = 0 k\mod n = 0 kmodn=0 的情况,最后可能会多算。
A C AC AC C o d e Code Code
#include<bits/stdc++.h>
using namespace std;
long long v[100001],a[100001];
int n,k;
int p,sum;
long long ans=0;//保险起见,开个long long
int main()
{
cin>>n>>k;
k--;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
v[x]=i;
//为了方便处理,把输入的 $v_i$ 改成了表示第 $i$ 扇门的钥匙编号
}
ans=v[1]-1;//这句很重要:ans的初始值
p=v[1];//p从1开始
v[n+1]=v[1];//第n+1扇门就是第1扇门
for(int i=2;i<=n+1;i++)
{
if(v[i]>=p)a[i-1]=a[i-2]+v[i]-p;
//钥匙的编号和当前的钥匙编号在一圈内:更新a数组:上一扇门加上这扇门需要尝试的次数
else a[i-1]=a[i-2]+v[i]+n-p;
//否则在下一圈
p=v[i];//更新p
}
ans+=a[n]*(k/n)+a[k%n];//算答案
cout<<ans;
return 0;
}
后记
各位读者,请不要吝啬您的赞和关注哦!
B y F H e n r y h \Huge\textcolor{gold}{By\ \ FHenryh} By FHenryh