人总在回忆里才看得清——2023河南萌新联赛第(一)场:河南农业大学题解。(缓慢更新中)

F题:松鼠排序

松鼠宝宝有一排n个大小不一的坚果,松鼠宝宝想把坚果从小到大排序,每次他会选择两个坚果a和b每次花费1点力气把这两个坚果交换,爱动脑筋的松鼠宝宝想知道他排完这n个坚果一共需要花费的最少力气是多少?

输入描述:

第一行一个整数n代表坚果数

接下来一行n个整数代表每个坚果的大小(每个坚果大小都不一样,即大小为1-n的一个排列)

1<=n<=1e5

坚果大小x,1<=x<=n

输出描述:

一行输出代表松鼠宝宝花费的最小力气

我们换成简单的话就是,将有序序列1,2,3.....n 随机打乱,每次操作可以交换任意两个数字,求最少的操作数使得这个序列回复原状。

我的思路就是,每次找到这个序列里面最小的数对应的位置(下标),然后把它和它原本位置上的数字交换。再找次小的数....交换....以此类推直到有序

假如为  5 4 3 2 1,最小的数是1,其下标为(5) 我们把它和 a[1](原本应该在的位置)交换

swap ( a[1] , a[5] ); swap ( a[2] , a[4] ); swap ( a[3] , a[3] );  注意,如果这个数在它原本的位置上,那么不计入答案。

所以应该是2种。

然后我们就能给出这样的代码

int ans;
int a[N];
int b[N];
int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
    }
    for (int i = 1; i <= n; i++)
    {
        int* it = find(a, a + n, i);//因为i从1开始,即就是从原有的序列开始,所以i是当前最小值
        swap(a[i], a[it - a]);
        if (i != it - a)ans++;
    }
    cout << ans << endl;
}

find 函数 是 查找 这个元素i 在序列 a 中的位置(下标) 然后返回这个下标的地址(16进制),我们要用它减去序列a的首地址,就得到  i 的下标了(10进制)

但是!!!find函数是O(n) 而外层还有一层循环 O(n)  也就是 O(n^2) 的时间复杂度无法通过本题。

在这样的基础上,我们加一个数组 b,用来存放 a中每个数字出现的位置。来代替find函数,这样就能达到O(n)的复杂度。

const int N = 1e5 + 10;
int ans;
int a[N];
int b[N];
int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        b[a[i]] = i;  一个数组用来存放 a数组每个数出现的位置
    }
    for (int i = 1; i <= n; i++)
    { 
          int t=a[i],k=b[i];  在每次循环之前,存好这两个值,因为后面交换会改变值。
                                b[i]指的是 i (即当前的最小值) 在a中的位置
                                a[k] 即 a[b[i]],其值是i
                     
        if(t!=a[k]) ans++;
          swap(a[i], a[k]);   我们要把i换到原来的位置上:
          swap(b[i],b[t]);     更新 a数组中每个数出现的位置
    }
   cout << ans << endl;
}

这两份代码的区别就是,我们手动用另一个数组实现了find函数的功能。

G题:
给定一个长度为n的01串,你需要选择一段任意长度(可以为0)的区间对其翻转,翻转后,求最长的一段连续的全是1的区间的长度。

输入描述:

输入共2行。
第一行一个整数n(1≤n≤106)n(1\leq n \leq 10^6)n(1≤n≤106)。
第二行一个长度为n的01序列。

输出描述:

输出一个整数,表示最长的长度

我的思路是参考了我们学校选拔赛的C题,那题的答案非0即1,是一个找规律的题,找规律到最后发现就两种情况。 

那么这题的思路也是类似的,无论你这个01序列是怎样的,我们只需要找出这里面最长的两段1就可以了。因为我们是选择一段任意长度的区间,所以始终可以把两段最长的1,拼接到一起。

那么现在的问题就是如何找到两端最长的1序列了。

1,双指针(好丑)

const int N =1e6+10;
int len[N],n,t;
string s;
int main()
{
    cin>>n; cin>>s;
    for(int i=0,j;i<n;i++ )
    {
        int ans=0;
        if(s[i]=='1')
        {
           for( j=i;j<n;)
           { 
               if(s[j]=='0')break;
               else if(s[j]=='1')
               {
                   ans=j-i+1;
                    j++;
               }
            }
             i=j;
             len[t++]=ans;
         }
    }
    sort(len,len+t);
    cout<<len[t-1]+len[t-2];
}

2,朴实无华的计数法(在输入01序列的时候就开始计数最长的序列)

const int N =1e6+10;
int len[N],t=0,ans=0,n;
char temp[N];
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>temp[i];
        if(temp[i]=='1')
        {
           ans++; len[t]=ans;
        }
       else
        {
            ans=0; t++;
        }
    }
    sort(len ,len+t+1);
    cout<<len[t]+len[t-1];
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

louisdlee.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值