Codeforces 820D 题解

本文介绍了一种针对数组操作的算法优化技巧,通过分析数组滚动过程中的D值变化规律,实现了从O(n^2)到O(n)的时间复杂度优化。文章详细解释了如何通过维护特定计数和巧妙的状态转移实现高效求解。

题意简述

定义一个数组的d值为这个数组中|所有数-下标|(两个竖线表示取绝对值,别看差了)的和。比如2 1 3 4的d值就是|2-1|+|1-2|+|3-3|+|4-4|=2。给定一个数组长度为n(n<=1e6),保证这个数组中有1~n的所有数字。请计算出:滚动(滚动一次a b c d变成b c d a)这个数组多少次使得d最小,一次输出滚动的次数和这个最小的d。
(这叫简述???总比嘤文题面好的多吧。。。)

数据

输入:
3
1 2 3
输出:
0 0

输入:
3
2 3 1
输出
0 1

输入:
3
3 2 1
输出:
2 1

思路

想想暴力的做法:不断滚动并求出d值,更新答案,O(n2)=O(TLE)O(n^2)=O(TLE)O(n2)=O(TLE)

按照老套路:如何优化这个暴力使得时间复杂度是O(AC)O(AC)O(AC)

Imagine this:如果我们只要求第一次的d值,然后每次O(1)O(1)O(1)或者O(logn)O(logn)O(logn)转移,那不就O(AC)O(AC)O(AC)了么!

但是它的转移好像毫无规律。。。等等,是毫无规律么?仔细想想,如果我们现在有llla[i]a[i]a[i]满足a[i]&gt;ia[i]&gt;ia[i]>i(除了最后一个,最后一个特判),那么整体滚动后每个∣a[i]−i∣|a[i]-i|a[i]i将会−1-11(原因是正的更小了,绝对值也更小了),则总共加起来就是−l-ll。当然,那些a[i]&lt;=ia[i]&lt;=ia[i]<=i的(记为rrr个)每个将会使ddd增加111(负的更多,绝对值增加),总共加起来就是+r+r+r。当然,我们珂以直接计算得r=n−l−1r=n-l-1r=nl1,即n−ln-lnl再去掉最后一个元素。代码中没有写出rrr,用n−l−1n-l-1nl1代替了。

这样,转移d就是O(1)O(1)O(1)。设最后一个是xxx,则d=d−l+(n−l−1)+∣x−1∣−∣x−n∣d=d-l+(n-l-1)+|x-1|-|x-n|d=dl+(nl1)+x1xn

那么,如何更新lll的值呢?

设一个ppp数组,p[i]p[i]p[i]表示原数组在移动了i次之后又多少个a[i]=ia[i]=ia[i]=i,则每次滚动后,lll要减去p[i]p[i]p[i]。特判最后一个(它太特殊了),还是记为xxx(跟上面的xxx是同一个),如果x!=1x!=1x!=1(在本题中这等价于x&gt;1x&gt;1x>1),则lll应该+1(因为xxx移到了111的位置)。注意,ppp数组也要更新,因为在这个xxx回到了开头又重新右移的时候也可能会出现x=x的下标x=x\text{的下标}x=x的下标的情况出现。为了考虑到循环,使用取模运算。(即p[(x−1+i)%n]++p[(x-1+i)\%n]++p[(x1+i)%n]++)。

然后更新答案即可(这个可别忘了!)。最后输出最小的d和滚动多少次。

代码:

#include<bits/stdc++.h>
#define N 1001000
#define int long long
using namespace std;

int n,a[N],p[N];

void Input()
{
    scanf("%I64d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%I64d",&a[i]);
    }
}
void Solve()
{
    int l=0,d=0;
    for(int i=1;i<=n;i++)
    {
        if (a[i]>i)
        {
            l++,p[a[i]-i]++;
        }
        d+=abs(a[i]-i);
    }
    int ansd=d,ansi=0;
    for(int i=1;i<n;i++)
    {
    	//开始转移
        int x=a[n-i+1];//取当前最后一个(不改变原数组)
        d-=l;
        d+=(n-l-1);

        d+=abs(x-1);
        d-=abs(x-n);//为了清楚,分开写

        l-=p[i];
        if (x!=1)
        {
            l++;
            p[(x-1+i)%n]++;
        }
        
        if (d<ansd)//更新答案
        {
            ansd=d;
            ansi=i;
        }
    }
    //解释了好多,代码却很短
    //"也不过如此啊!"你珂能会想
    //这就是思维题的难处
    //实际上这题思维量非常大
    //我看着题解看着代码在学校里面用了几天才真正理解了这个题。。。
    printf("%lld %lld\n",ansd,ansi);
}
main()
{
    Input();
    Solve();
    return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值