HDU 1394 (线段树求逆序对+思维)
题目:
意思是,给你N个数,要求统计它的所有形式的逆序对的最小值。它的所有形式的意思是,不断将数组开头的第一个数放到数组的最后面,共有n种形式。
朴素思路
线段树求逆序对:
1、建树时每个区间初始值为0。区间维护的是当前数值是否插入。
2、读入数据,[1,n] ,按照题目的要求来
3、对这n个数进行逆序对数的求解:
① 对每一个数 a[i] 进行插入,对应的区间标记为1.
② 这个数a[i] 的逆序对个数为区间[a[i] + 1, n-1]的标记数,即query(a[i]+1, n-1, …)
③ 对所有n个数的逆序对个数求和。
4、对所有的形式的逆序对个数求最小值。
我的代码
(结果tle了)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 1<<30
const int maxn = 5e3+5;
int n, minn, num;
int cnt[maxn<<2];
int a[maxn];
void Build(int l, int r, int pos){
if(l == r){ cnt[pos] = 0; return; }
int m = (l+r)>>1;
Build(l, m, pos<<1);
Build(m+1, r, pos<<1|1);
cnt[pos] = cnt[pos<<1] + cnt[pos<<1|1];
}
void update(int goal, int x, int l, int r, int pos){
if(l == r){
cnt[pos] += 1;return;
}
int m = (l+r)>>1;
if(goal <= m) update(goal, x, l, m, pos<<1);
else update(goal, x, m+1, r, pos<<1|1);
cnt[pos] = cnt[pos<<1] + cnt[pos<<1|1];
}
int query(int L, int R, int l, int r, int pos){
if(L <= l && r <= R){
return cnt[pos];
}
int m = (l+r)>>1;
int ans = 0;
if(L <= m) ans += query(L, R, l, m, pos<<1);
if(R > m) ans += query(L, R, m+1, r, pos<<1|1);
return ans;
}
int main(){
while(cin >> n){
for(int i = 0; i < n; i++){
cin >> a[i];
a[i+n] = a[i];
}
minn = inf;
for(int i = 0; i < n; i++){
Build(0, n-1, 1);
num = 0;
for(int j = i; j <= n+i-1; j++){
update(a[j], 1, 0, n-1, 1);
num += query(a[j]+1, n-1, 0, n-1, 1);
}
minn = min(minn, num);
}
cout << minn << endl;
}
}
改进1
每次都算n个数的逆序对个数,太费时了。
可以发现,每次将第一个数字移到最后一个数字时,整个序列的逆序对个数 -= a[1] 后面有多少个比它小的,再 += a[1] 后面有多少个比它大的。
所以将程序改成了这样:
//...同上
int main(){
while(cin >> n){
Build(0, n-1, 1);
num = 0;
for(int i = 0; i < n; i++){
cin >> a[i];
update(a[i], 1, 0, n-1, 1);
num += query(a[i]+1, n-1, 0, n-1, 1);
}
//插入的同时先算出第一次序列的逆序对个数
minn = num;
//注意这里只需要计算n-1次序列就可以了
for(int i = 0; i < n-1; i++){
update(a[i], -1, 0, n-1, 1); //每次计算前先把第i个元素“删除”,相当于该元素还未插入,待插入。
num += query(a[i]+1, n-1, 0, n-1, 1);
num -= query(0, a[i], 0, n-1, 1);
update(a[i], 1, 0, n-1, 1);//计算后把第i个元素重新插入,相当于把第i个元素插入到最后面。
minn = min(minn, num);
}
cout << minn << endl;
}
}
改进2
再改进,可以发现,因为这是一个0 ~ n-1的序列,所以我们是可以O(1)知道第一个数字后面有多少个数比它大,有多少个数比它小的。
比如样例,n=10,对于 1 到 n 的序列来说,第一个数字是1,则后面比1大的必然有9个,比1小的必然有0个。
可以得出,对于元素a[i],比它大的有 n - a[i] 个,比它小的有 a[i] - 1 个。
注意本题下标从0开始,所以↑有变动。变成了:对于元素a[i],比它大的有 n - a[i] - 1个,比它小的有 a[i] 个。
代码如下:
int main(){
while(cin >> n){
Build(0, n-1, 1);
num = 0;
for(int i = 0; i < n; i++){
cin >> a[i];
update(a[i], 1, 0, n-1, 1);
num += query(a[i]+1, n-1, 0, n-1, 1);
}
minn = num;
for(int i = 0; i < n-1; i++){
num += n - a[i]-1;
num -= a[i];
minn = min(minn, num);
}
cout << minn << endl;
}
}