题目地址:
http://codeforces.com/contest/820/problem/D
题意:
给一个由 [1,n] 共 n(2<=n<=106) 个数组成的排列p,定义这个排列的差值是 ∑i=ni=1|p[i]−i| ,现在我们可以对这个排列进行平移(把排列的最后一个数放到排列的最前面),问这个排列的最小差值是多少。
思路:
这题看题解还有点迷,其实也不是很难,就是要自己动手码,代码多改改,多思考下应该都能做出来。
自己随便写几个排列模拟下,不难发现,一次平移其实就是
p[i]>i
中
i
的值自增了1(除了最后面那个数要特殊考虑),那么所有
p[i]−i>0
的数都会使
cost[i]=|p[i]−i|
小1,反之,所有
p[i]−i<=0
的数都会使
cost[i]=|p[i]−i|
加1,那么我们用一个pos记录
p[i]−i>0
的数的个数,neg记录
p[i]−i<=0
的个数。
特殊考虑一下最后一个数,这个数的 cost[n]=|p[i]−n|=n−p[i] ,一定是neg,又因为我们要对它特殊考虑,所以不在 pos 和 neg 的计数中考虑,所以每次平移 sum=sum−pos+(neg−1) 。并且 p[n] 移到了 1 的位置,所以特判一下 p[n] 的大小,对 pos 和 neg 进行修改。
接着就是特殊考虑 p[n] 对 cost 的影响,很简单, sum=sum−|p[n]−n|+p[n]−1 就好了。
最后还是 pos 和 neg 值的变化,我们用一个数组 ones[] 记录 p[i]−i 的值,每次把当前差值为1的数从 pos 中拿出,放入 neg 中就好了。特别注意 ones[] 数组要开两倍大小。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cstdlib>
using namespace std;
//#define abs(x) ((x) < 0 ? -(x) : (x))
#define MS(x, y) memset(x, y, sizeof(x))
typedef long long LL;
const int MAXN = 1e6 + 5;
int n;
int p[MAXN], ones[MAXN << 1];
int main() {
while (~scanf("%d", &n)) {
LL sum = 0, mn;
int mn_idx = 0, pos = 0, neg = 0;
MS(ones, 0);
for (int i = 1; i <= n; ++i) {
scanf("%d", p + i);
sum += abs(p[i] - i);
if (p[i] > i) ++pos;
else ++neg;
if (p[i] >= i) ++ones[p[i] - i];
}
mn = sum;
for (int i = 1; i <= n; ++i) {
sum = sum - pos + neg - 1;
sum = sum - abs(p[n - i + 1] - n) + p[n - i + 1] - 1;
if (p[n - i + 1] != 1) {
++pos;
--neg;
}
pos -= ones[i];
neg += ones[i];
++ones[p[n - i + 1] - 1 + i];
if (sum < mn) {
mn = sum;
mn_idx = i;
}
}
printf("%I64d %d\n", mn, mn_idx);
}
}