NKOJ 3937 为何奶牛要穿过马路1 (树状数组)

P3937为何奶牛要穿过马路1

问题描述

有一条笔直道路穿过约翰的农场。
道路的一侧有N个牛棚,编号1到N的N头奶牛分布在这N个牛棚里,每个牛棚只有一头牛。
道路的另一侧也有N个牛棚,编号1到N的N头奶牛分布在这N个牛棚里,每个牛棚只有一头牛。
相同编号的奶牛经常穿过马路互相拜访,由于奶牛们穿非常频发地穿马路,导致奶牛们经常相撞(线路交叉导致)。约翰想重新布置一下牛棚,减少碰撞事故。
约翰打算采取“循环移位”的方式布置牛棚。所谓循环移位是指,比如道路一侧有7个牛棚,里面居住的奶牛编号分别是3、7、1、2、5、4、6,如果约翰打算将牛棚往右移动2个位置,那么移位后,新的顺序是4、6、3、7、1、2、5。只能移动其中一侧的牛棚。。
请你帮约翰计算一下,怎样移动才能使得奶牛将相互访问的交叉线路最少,输出这个最少交叉数。

输入格式

第一行,一个整数N(1≤N≤100,000)
接下来N行,每行一个整数,按从左往右的顺序给出了道路一侧奶牛的分布情况。
接下来N行,每行一个整数,按从左往右的顺序给出了道路另一侧奶牛的分布情况。

输出格式

一个整数,表示所求最少交叉线路数。

样例输入 1

5
5
4
1
3
2
1
3
2
5
4

样例输出 1

0

样例输入 2

7
1
3
5
7
2
4
6
1
2
3
4
5
6
7

样例输出 2

6


只能转一边,那么显然分开讨论。
假设转上面的序列。
令上面的序列为$A$,下面的为$B$,那么令$C[i]$表示$A[i]$在$B$中出现的位置,那么显然交叉数就是$C$序列的逆序对数,那么用树状数组算出来。
然后考虑转动$C$,那么显然把$C$序列的最后一个数拿出来会减少的逆序对数是已知的,然后将他加到序列的开头会增加的逆序对数也是已知的,那么转动一次后的逆序对数可以$O(1)$算出来,那么直接枚举转动即可。

转动下面的序列同样处理,取最小值即可。


代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<stdio.h>
#include<cstring>
#define ll long long
#define min(a,b) (a<b?a:b)
#define N 200005
using namespace std;
ll n,A[N],B[N],C[N],D[N],E[N];
ll MD(ll x,ll d)
{for(ll i=x;i<=n;i+=(i&-i))D[i]+=d;}
ll GS(ll x)
{
ll i,sum=0;
for(i=x;i;i-=(i&-i))sum+=D[i];
return sum;
}
int main()
{
ll i,j,k,ans=0,tmp;
scanf("%lld",&n);
for(i=1;i<=n;i++)scanf("%lld",&A[i]),E[A[i]]=i;
for(i=1;i<=n;i++)scanf("%lld",&B[i]),C[B[i]]=i;
for(i=1;i<=n;i++)A[i]=C[A[i]];
for(i=1;i<=n;i++)B[i]=E[B[i]];

for(i=1;i<=n;i++)
{
ans+=i-1-GS(A[i]);
MD(A[i],1);
}
tmp=ans;
for(i=1;i<=n;i++)
{
tmp+=n-2*A[i]+1;
ans=min(ans,tmp);
}

memset(D,0,sizeof(D));tmp=0;
for(i=1;i<=n;i++)
{
tmp+=i-1-GS(B[i]);
MD(B[i],1);
}
ans=min(ans,tmp);
for(i=1;i<=n;i++)
{
tmp+=n-2*B[i]+1;
ans=min(ans,tmp);
}
printf("%lld",ans);
}