Why Did the Cow Cross the Road I P

Why Did the Cow Cross the Road I PWhy\ Did\ the\ Cow\ Cross\ the\ Road\ I\ P

題目鏈接:luogu P3656luogu\ P3656 \ jzoj 6684jzoj\ 6684

題目

爲什麼牛要過馬路?我們可能永遠都不知道全部原因,但肯定的是,農民約翰的牛經常穿過馬路。事實上,他們經常過馬路,當他們的路徑交叉時,他們經常碰到對方,這是約翰希望改進的情況。

農夫約翰將奶牛分成了NNN個不同的品種1N100000(1\le N\le 100000),他的每一個田地都是專門爲一種特定的品種放牧; 例如,專門用於品種1212奶牛的田地只能用於品種1212的奶牛,而不能用於任何其他品種的奶牛。一條長長的路穿過他的農場。道路一側有NN個田地(每個品種一個),道路另一側也有NN個田地(每個品種也有一個)。所以,當一頭牛穿過馬路,它會穿過特定品種的兩個田地。

如果農民約翰更仔細的研究他的計劃,他會在道路兩旁訂購同品種田地,所以每個品種的兩個田野將直接穿過彼此的道路。這樣就可以讓奶牛隨意過馬路,而不會有不同品種的牛碰撞在一起。唉,但現在道路兩邊的田地可能不一樣,所以農民約翰明白,可能會有一對品種(a,b)(a,b)是交叉的當且僅當是一對不同的品種,且道路上一邊田地品種aa上的任何路線都與道路另一邊田地品種bb的任何路線相交 。

農民約翰希望儘量減少品種的交叉對數。由於物流方面的原因,他表示,他可以在道路的一邊移動牛,所以那邊的田地可以“循環轉移”。也就是說,對於一個給定的常值kk來說,循環轉移是指每頭牧場裏的牛都會向前移動kk個田野,而最前面的kk只奶牛會移動到最後,因爲向前移動的奶牛已經填補了前kk個田地。比如,如果道路一側的田地移動前的排列爲3,7,1,2,5,4,63,7,1,2,5,4,6,假設kk22,新排列將爲4,6,3,7,1,2,54,6,3,7 ,1,2,5.請編程確定在道路一側的田野適當循環移動後存在的品種交叉對的最小值。

輸入

一行包含N(1N100,000)N (1 \leq N \leq 100,000),接下來NN行按順序描述了小路一旁田地的品種的IDID號,每一個IDID號是一個在1...N1...N之間的整數。倒數NN行描述了小路另一旁田地的品種的IDID號。

輸出

循環移動道路側的田野後,請輸出可能的品種交叉對的最小數量(兩邊均可移動)

樣例輸入

5
5
4
1
3
2
1
3
2
5
4

樣例輸出

0

思路

這道題要用樹狀數組。

我們先讓pl[b[i]]=ipl[b[i]] = ipar[i]=pl[a[i]]par[i] = pl[a[i]],那麼par[i]par[i]就代表a[i]a[i]在序列bb中的位置。
那麼我們就可以通過樹狀數組求出par[i]par[i]的逆序對,而它的逆序對就是當前的交叉對。
那我們只要不斷的把最後面的數移到最前面,然後維護逆序對個數,就可以了。

至於怎麼維護,就是把原來的逆序對(npar[i])+(par[i]1)- (n - par[i]) + (par[i] - 1)
(一個是把後面拿掉所扣的,另一個是放在前面所加的)

不過還有一個就是,因爲aabb兩個序列都可以動,所以我們要分別試讓aa序列動和讓bb序列動的情況。
(不過好像也不用,但我沒試,不知道能不能只動一個)

代碼

#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long

using namespace std;

int n, a[100001], b[100001], pl[100001], par[100001], ni[100001];
ll ans;

int get(int now) {//單點求值
	int answer = 0;
	for (; now <= n; now += now & -now)
		answer += ni[now];
	return answer;
}

void build(int now, int add) {//單點加值
	for (; now; now -= now & -now)
		ni[now] += add;
}

ll work() {
	memset(ni, 0, sizeof(ni));//初始化
	ll an = 0;
	for (int i = 1; i <= n; i++) {//求出當前逆序對個數
		an += get(par[i]);
		build(par[i], 1);
	}
	ll getnew = an;
	for (int i = n; i >= 0; i--) {//移數
		getnew = getnew - n + par[i] * 2 - 1;//得到新的逆序對的數量
		an = min(getnew, an);//找到逆序對最少的情況
	}
	return an;
}

int main() {
//	freopen("mincross.in", "r", stdin);
//	freopen("mincross.out", "w", stdout);
	
	scanf("%d", &n);//讀入
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);//讀入
	for (int i = 1; i <= n; i++) scanf("%d", &b[i]);//讀入
	
	for (int i = 1; i <= n; i++) pl[b[i]] = i;
	for (int i = 1; i <= n; i++) par[i] = pl[a[i]];//初始化
	ans = work();
	
	for (int i = 1; i <= n; i++) pl[a[i]] = i;
	for (int i = 1; i <= n; i++) par[i] = pl[b[i]];//反過來也要一次(因爲b也可以動)
	ans = min(ans, work());
	
	printf("%lld", ans);//輸出
	
//	fclose(stdin);
//	fclose(stdout);
	
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章