线段树求解区间内乱序求逆序数及其转化变形问题

HDU 1394  Minimum Inversion Number

题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=1394

题意:

给定一个区间,并将区间内数字乱序排列,每次操作可以将乱序数组的第一个放置到数组的最后,问通过操作能形成的最小逆序数是多少。

在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。比如一个序列为4 5 1 3 2, 那么这个序列的逆序数为7,逆序对分别为(4, 1), (4, 3), (4, 2), (5, 1), (5, 3), (5, 2),(3, 2)。

 

思路:

通过模拟上述操作可以发现,每次把第一个数放到最后,逆序数的的变化量应该是(-a[i]+n-(a[i]+1)),即sum=sum-a[i]+n-(a[i]+1);

1.暴力

遍历一次乱序数组,把当前状态下的逆序数求出来,然后进行n次题目所给操作,将第一个数放置到最后求逆序数,取最小值即可。

2.线段树

初始建树,使每个点的num值都初始化为0,然后从数组末尾进行询问更新,询问是否当前存在多少个比自身小的数,然后更新当前点。每次询问结果的总和即为数组当前状态下的逆序数。然后同理遍历操作取最小值。

AC代码

暴力代码就不放了,下面是线段树:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cctype>
#include<iomanip>
#include<map>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<set>
#include<cctype>
#include<string>
#include<stdexcept>
#include<fstream>
#include<sstream>
#include<sstream>
#define mem(a,b) memset(a,b,sizeof(a))
#define debug() puts("what the fuck!")
#define dedebug() puts("what the fuck!!!")
#define ll long long
#define ull unsigned long long
#define speed {ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); };
using namespace std;
const double PI = acos(-1.0);
const int maxn = 2e6 + 10;
const int N = 5e3 + 10;
const ll INF = 1e18;
const ll mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const double eps_0 = 1e-9;
const double gold = (1 + sqrt(5)) / 2;
template<typename T>
inline void rd(T& x) {
	int f = 1; x = 0; char c = getchar();
	while (c<'0' || c>'9') { f = -1; c = getchar(); }
	while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
	x *= f;
}
template<typename T>
inline T gcd(T a, T b) {
	return b ? gcd(b, a % b) : a;
}
int n;
int ans = 0;
int a[N];
struct node {
	int l, r;
	int num;
}tree[maxn<<2];
void push_up(int i) {
	tree[i].num = tree[i << 1].num + tree[i << 1 | 1].num;
}
void build(int i, int l, int r) {
	tree[i].l = l;
	tree[i].r = r;
	tree[i].num = 0;
	if (l == r)return;
	int mid = (l + r) >> 1;
	build(i << 1, l, mid);
	build(i << 1 | 1, mid + 1, r);
	push_up(i);
}
void query(int i, int l, int r) {
	if (tree[i].l == tree[i].r) {
		ans += tree[i].num;
		return;
	}
	int mid = (tree[i].l + tree[i].r) >> 1;
	if (l > mid)query(i << 1 | 1, l, r);
	else if (r <= mid)query(i << 1, l, r);
	else {
		query(i << 1, l, mid);
		query(i << 1 | 1, mid + 1, r);
	}
}
void update(int i, int dis) {
	if (tree[i].l == tree[i].r && tree[i].l == dis) {
		tree[i].num++;
		return;
	}
	int mid = (tree[i].l + tree[i].r) >> 1;
	if (dis <= mid) {
		update(i << 1, dis);
	}
	else {
		update(i << 1 | 1, dis);
	}
	push_up(i);
}

signed main() {
	while (~scanf("%d", &n)) {
		build(1, 1, n);
		for (int i = 1; i <= n; ++i) {
			scanf("%d", &a[i]);
		}
		int sum = 0;
		for (int i = n; i >= 1; --i) {
			ans = 0;
			int dis = a[i] + 1;
			if (dis != 1)query(1, 1, dis);//询问比dis小的数存在多少个
			sum += ans;
			update(1,dis);
		}
		//cout << sum << " " << tree[1].num << endl;
		int ans = sum;
		for (int i = 1; i <= n; ++i) {
			sum = sum - a[i] + n - (a[i] + 1);
			ans = min(ans, sum);
		}
		printf("%d\n", ans);
	}
	return 0;
}

 

POJ 2481 Cows

题目链接http://poj.org/problem?id=2481

题意:

给定n个区间,若一个区间a有一个子集区间b,则说明a区间比b区间强壮,问每个区间在给定的所有区间中,有几个区间比自己强壮。

 

思路:

(想了好久,就是不知道如何转化到线段树上!!!! )

设区间左边界为st,右边界为ed,根据st升序排序,ed降序排序,那么就可以保证按序遍历时,前面的区间比后面的区间要更长,那么就相当于在当前区间,强壮区间只可能存在于当前区间前面,那么

就相当于时我们固定了左端点st,我们只需要询问更新右端点ed,即判断在当前右端点ed前面有多上个比ed大的值,所得个数即为当前区间的强壮区间个数。那么就和求逆序数差不多的操作,区间询问,单点修改。

 

 AC代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cctype>
#include<iomanip>
#include<map>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<set>
#include<cctype>
#include<string>
#include<stdexcept>
#include<fstream>
#include<sstream>
#include<sstream>
#define mem(a,b) memset(a,b,sizeof(a))
#define debug() puts("what the fuck!")
#define dedebug() puts("what the fuck!!!")
#define ll long long
#define ull unsigned long long
#define speed {ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); };
using namespace std;
const double PI = acos(-1.0);
const int maxn = 2e6 + 10;
const int N = 5e3 + 10;
const ll INF = 1e18;
const ll mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const double eps_0 = 1e-9;
const double gold = (1 + sqrt(5)) / 2;
template<typename T>
inline void rd(T& x) {
    int f = 1; x = 0; char c = getchar();
    while (c<'0' || c>'9') { f = -1; c = getchar(); }
    while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    x *= f;
}
template<typename T>
inline T gcd(T a, T b) {
    return b ? gcd(b, a % b) : a;
}
int n;
int ans[maxn];
struct cow {
    int st, ed;
    int id;
    bool operator<(const cow& a)const {
        if (st == a.st)return ed > a.ed;
        return st < a.st;
    }
}a[maxn];
struct node {
    int l, r;
    int sum;
}tree[maxn << 2];
void push_up(int i) {
    tree[i].sum = tree[i << 1].sum + tree[i << 1 | 1].sum;
}
void build(int i, int l, int r) {
    tree[i].l = l;
    tree[i].r = r;
    if (l == r) {
        tree[i].sum = 0;//初始化为0
        return;
    }
    int mid = (l + r) >> 1;
    build(i << 1, l, mid);
    build(i << 1 | 1, mid + 1, r);
    push_up(i);
}
int query(int i, int l, int r) {
    int res = 0;
    if (l <= tree[i].l && r >= tree[i].r)return tree[i].sum;//区间满足即可返回sum值!!!
    int mid = (tree[i].l + tree[i].r) >> 1;
    if (l <= mid)res += query(i << 1, l, r);
    if (r > mid)res += query(i << 1 | 1, l, r);
    return res;
}
void update(int i, int dis) {
    //cout << "i:" << i << " l:" << tree[i].l << " r:" << tree[i].r << " sum:" << tree[i].sum << endl;
    if (tree[i].l == tree[i].r) {
        tree[i].sum++;
        //cout << "i:" << i << " l:" << tree[i].l << " r:" << tree[i].r << " sum:" << tree[i].sum << endl;
        return;
    }
    int mid = (tree[i].l + tree[i].r) >> 1;
    if (dis <= mid)update(i << 1, dis);
    else update(i << 1 | 1, dis);
    push_up(i);
}
signed main() {
    while (scanf("%d", &n), n) {
        mem(ans, 0);
        for (int i = 1; i <= n; ++i) {
            scanf("%d%d", &a[i].st, &a[i].ed);
            a[i].st++;
            a[i].ed++;
            a[i].id = i;
        }
        build(1, 1, n);
        sort(a + 1, a + 1 + n);
        //for (int i = 1; i <= n; ++i) {
        //    cout << a[i].id << " " << a[i].st << " " << a[i].ed << endl;
        //}
        for (int i = 1; i <= n; ++i) {
            if (i != 1 && a[i].st == a[i - 1].st && a[i].ed == a[i - 1].ed) {
                ans[a[i].id] = ans[a[i - 1].id];//如果区间相同,则可以直接得到答案
            }
            else {
                ans[a[i].id] = query(1, a[i].ed, n);
                //cout << a[i].id << " " << ans[a[i].id] << endl;
            }
            update(1, a[i].ed);
        }
        for (int i = 1; i <= n; ++i) {
            printf("%d%c", ans[i], " \n"[i == n]);
        }
    }
    return 0;
}

 

 

POJ 2182 Lost Cows

题目链接http://poj.org/problem?id=2182

题意:

n只牛用1-n来标号,接下来n-1行,分贝告诉你从第2到第n只牛左边有几个比它序号小的牛,求每个位置牛的编号

思路:

即求逆序数的变形,之前都是给定你数组,让你求逆序数,这次是把每个位置的逆序对个数告诉我们,让我们还原数组。

开一棵线段树,初始化num值为当前区间内含有的数字个数,然后我们从给定数组末尾去查询更新,每次更新我们查找到第a[i]小的数,返回值并减少一个数字量。

 

AC代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cctype>
#include<iomanip>
#include<map>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<set>
#include<cctype>
#include<string>
#include<stdexcept>
#include<fstream>
#include<sstream>
#include<sstream>
#define mem(a,b) memset(a,b,sizeof(a))
#define debug() puts("what the fuck!")
#define dedebug() puts("what the fuck!!!")
#define ll long long
#define ull unsigned long long
#define speed {ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); };
using namespace std;
const double PI = acos(-1.0);
const int maxn = 2e6 + 10;
const int N = 5e3 + 10;
const ll INF = 1e18;
const ll mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const double eps_0 = 1e-9;
const double gold = (1 + sqrt(5)) / 2;
template<typename T>
inline void rd(T& x) {
    int f = 1; x = 0; char c = getchar();
    while (c<'0' || c>'9') { f = -1; c = getchar(); }
    while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    x *= f;
}
template<typename T>
inline T gcd(T a, T b) {
    return b ? gcd(b, a % b) : a;
}
int n;
int a[maxn];
int ans[maxn];
struct node {
    int l, r;
    int num;
}tree[maxn << 2];
void build(int i, int l, int r) {
    tree[i].l = l;
    tree[i].r = r;
    tree[i].num = r - l + 1;//更新num值为区间内数字个数
    //cout << i << " left:" << l << " right:" << r << " " << tree[i].num << "**" << endl;
    if (l == r)return;
    int mid = (l + r) >> 1;
    build(i << 1, l, mid);
    build(i << 1 | 1, mid + 1, r);
}
int query(int i, int dis) {
    tree[i].num--;//删除一个数字量
    //cout << i << " left:" << tree[i].l << " right:" << tree[i].r << " " << tree[i].num << endl;
    if (tree[i].l == tree[i].r) {
        return tree[i].l;//查找到该值,返回.
    }
    if (tree[i << 1].num >= dis)return query(i << 1, dis);
    else return query(i << 1 | 1, dis - tree[i << 1].num);
}
signed main() {
    while (~scanf("%d", &n)) {
        a[1] = 1;
        for (int i = 2; i <= n; ++i) {
            scanf("%d", &a[i]);
            a[i]++;
        }
        build(1, 1, n);
        for (int i = n; i >= 1; --i) {
            ans[i] = query(1, a[i]);
        }
        for (int i = 1; i <= n; ++i) {
            printf("%d\n", ans[i]);
        }
    }
    return 0;
}

 

POJ 2828 Buy Tickets

题目链接http://poj.org/problem?id=2828

题意:

一个长度为n的空数组,每次给你一个pos和val,将val插入到pos位置,最后输出该数组。

思路:

和上一题比较像,只是这次有了具体的其他值,依然可以按照上题的做法做,从后往前遍历,当前人可以排在i位置,那么说明它前面必然有i个空位置可以站,那么我们就从后往前依次询问更新,和上题一样,每次更新,位置就少了一个,注意询问返回值是数组中的下标,最后将val值与下标映射对应输出即可。

 

AC代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cctype>
#include<iomanip>
#include<map>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<set>
#include<cctype>
#include<string>
#include<stdexcept>
#include<fstream>
#include<sstream>
#include<sstream>
#define mem(a,b) memset(a,b,sizeof(a))
#define debug() puts("what the fuck!")
#define dedebug() puts("what the fuck!!!")
#define ll long long
#define ull unsigned long long
#define speed {ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); };
using namespace std;
const double PI = acos(-1.0);
const int maxn = 2e6 + 10;
const int N = 5e3 + 10;
const ll INF = 1e18;
const ll mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const double eps_0 = 1e-9;
const double gold = (1 + sqrt(5)) / 2;
template<typename T>
inline void rd(T& x) {
    int f = 1; x = 0; char c = getchar();
    while (c<'0' || c>'9') { f = -1; c = getchar(); }
    while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    x *= f;
}
template<typename T>
inline T gcd(T a, T b) {
    return b ? gcd(b, a % b) : a;
}
int n;
int pos[maxn];
int val[maxn];
struct node {
    int l, r;
    int num;
}tree[maxn << 2];
void build(int i, int l, int r) {
    tree[i].l = l;
    tree[i].r = r;
    tree[i].num = r - l + 1;
    if (l == r)return;
    int mid = (l + r) >> 1;
    build(i << 1, l, mid);
    build(i << 1 | 1, mid + 1, r);
}
int query(int i, int dis) {
    tree[i].num--;
    if (tree[i].l == tree[i].r)return tree[i].l;
    if (tree[i << 1].num >= dis)return query(i << 1, dis);
    else return query(i << 1 | 1, dis - tree[i << 1].num);
}
int ans[maxn];
signed main() {
    while (~scanf("%d", &n)) {
        mem(ans, 0);
        for (int i = 1; i <= n; ++i) {
            scanf("%d%d", &pos[i], &val[i]);
        }
        build(1, 1, n);
        for (int i = n; i >= 1; --i) {
            int p = query(1, pos[i] + 1);
            //cout << p << "**" << endl;
            ans[p] = val[i];
        }
        for (int i = 1; i <= n; ++i) {
            printf("%d%c", ans[i], " \n"[i == n]);
        }
    }
    return 0;
}

 

posted @ 2021-04-06 23:47  Fire_WCNMD  阅读(243)  评论(0)    收藏  举报