近期练习赛的时候,多次碰见的树状数组的题,借以下两道加深一下理解。
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)