树状数组应用

近期练习赛的时候,多次碰见的树状数组的题,借以下两道加深一下理解。

        1 HDU - 6918

         题目链接 

        https://acm.hdu.edu.cn/showproblem.php?pid=6918

 

         题目大意: 

先求出原序列的逆序对数,然后进行交换操作,输出操作过程中的最小的逆序对数的值。

        刚看见这道题的时候,便想到用树状数组求逆序对,但是在交换过程中,不知道怎么处理,便放弃了,后来才看见范围只用100,直接暴力即可。

        计算交换过程中的逆序对数目的变化,在最后进行最小值更新。

        由于题目没说原序列中是否会存在相等的数,所以在用输入顺序离散化的时候,比较函数从大到小排序。要把数目相等,较晚输入的放在前面,这样可以减少重复计算。

        比如1 3 3,按输入离散化后分别对应1 2 3。sort后如果为 2 3 1,那么在查询过程中,会多计算一次,而原本的逆序对应该是0,所以答案会出错。

Code:

        

#include<stdio.h>
#include<string.h>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<stack>
#include<vector>
#define ll long long
#define pll pair < ll , ll >
#define buff ios::sync_with_stdio(false);
using namespace std;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
ll n,m,k;
ll a[N],c[N];
ll dp[N];
struct ch
{
	ll x,y;
}s[N];
bool cmp(ch a,ch b) // 比较函数
{
	if(a.x == b.x)
	{
		return a.y > b.y; // 相同的话位置大的放前面
	}
	return a.x > b.x;
}
ll lowbits(ll x) // 树状数组的模板
{
	return x & (-x);
}
void modify(ll x,ll k)
{
	for(int i = x; i <= n;i += lowbits(i))
	{
		c[i] += k;
	}
}
ll query(ll l,ll r)
{
	ll sum = 0;
	for(int i = r; i >= l; i -= lowbits(i))
	{
		sum += c[i];
	}
	return sum;
}
int main()
{
	buff;
	ll t;
	cin >> t;
	while(t --)
	{
		cin >> n;
		for(int i = 0; i <= n; i ++)
		{
			c[i] = 0;
		}
		for(int i = 1; i <= n;i ++)
		{
			cin >> a[i];
			s[i].x = a[i];
			s[i].y = i;
		}
		cin >> m;
		sort(s + 1,s + 1 + n, cmp);
		ll ans = 0;
		for(int i = 1; i <= n; i ++)
		{
			modify(s[i].y,1);
			ans += query(1,s[i].y - 1);
		}
	//	cout << ans << "\n";
		ll ans1 = ans;
		while(m --) // 暴力模拟在交换过程中,对逆序对产生的影响;
		{
			ll l,r;
			cin >> l >> r;
			if(l == r)
			{
				continue;
			}
			if(l > r) // 确保l < r;
			{
				swap(l,r);
			}
			for(int i = l + 1; i < r; i ++)
			{
				if(a[l] > a[i])
				{
					ans --;
				}
				if(a[l] < a[i])
				{
					ans ++;
				}
			}
			for(int i = l + 1;i < r; i ++)
			{
				if(a[r] > a[i])
				{
					ans ++;
				}
				if(a[r] < a[i])
				{
					ans --;
				}
			}
			if(a[l] > a[r])
			{
				ans --;
			}
			if(a[l] < a[r])
			{
				ans ++;
			}
			swap(a[l],a[r]);// 交换
			ans1 = min(ans1,ans); // 更新
		}
		cout << ans1 << "\n";
	}
} 

2 - CodeForces - 1579E2

        题目链接 : Problem - 1579E2 - Codeforces

 

        题目大意 :

        按照数组的顺序,选择从前端压入还是从后端压入,求可能的逆序对数的最小值;

        首先我们注意到数据范围会有负数,那么说明我们要进行离散化,把负数转换为正值,这样在进行树状数组的统计时,才不会出现数组越界的导致RE的问题。

        另外本题不能进行坐标的离散化(或者可能是我太菜了),在用坐标离散化的时候,由于本题可以前后压入,会导致数据出错(maybe我没处理好)。

        本题使用sort + unique的离散化。在进行树状数组的逆序对板子即可。

Code :

        

#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<string.h>
#define ll long long
#define du double
#define pll pair < ll , ll >
#define buff ios::sync_with_stdio(false)
using namespace std;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
ll a[N],b[N],c[N];
ll n,d,m;
ll lowbits(ll x)
{
	return x & (-x);
}
void modify(ll x,ll k)
{
	for(int i = x; i <= n; i += lowbits(i))
	{
		c[i] += k;
	}
}
ll query(ll l,ll r)
{
	ll sum = 0;
	for(int i = r; i >= l; i -= lowbits(i))
	{
		sum += c[i];
	}
	return sum;
}
int main()
{
    buff;
	ll t;
	cin >> t;
	while(t --)
	{
		cin >> n;
		for(int i = 0;i <= n; i ++)// 初始化树状数组
		{
			c[i] = 0;
		}
		for(int i = 1; i <= n; i ++)
		{
			cin >> a[i];
			b[i] = a[i];
		}// 离散化
		sort(a + 1,a + 1 + n);
		ll m = unique(a + 1,a + 1 + n) - a - 1;
		for(int i = 1; i <= n; i ++)
		{
			b[i] = lower_bound(a + 1,a + 1 + m,b[i]) - a; 
		//	cout << b[i] << " ";
		}
		ll ans = 0;
		for(int i = 1; i <= n; i ++)
		{
			modify(b[i],1);
			ll x = query(1,b[i] -1);
			ll y = query(1,n) - query(1,b[i]);
			ans += min(x,y);// 判断是从前压和从后压入那个值更小
		}
		cout << ans << "\n";
			
	} 
}

        近期是EC的比赛,看着他们打线下好眼红,希望有一天我可以追星成功(jiangly)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值