2019河北省程序设计竞赛
本题运用树状数组求逆序对数的方法,如果不了解树状数组,可以先去学一下。
求每个男生扔出飞机后可能与之相撞的飞机数,分为两部分求:
1、男生x左边的每个男生i 如果男生i喜欢的女生所在位置大于男生x喜欢女生的所在位置,即pos[like[i]]>pos[like[x]],则可能相撞,计数+1;(左边与之构成的逆序对数)
2、男生x右边的每个男生i,如果男生i喜欢的女生所在位置小于男生x喜欢女生的所在位置,即pos[like[i]]<pos[like[x]],则可能相撞,计数+1;(右边与之构成的逆序对数)
代码部分处理了like数组,用每个女生的座位号来代替女生的id号,即like[i]=pos[like[i]],后面讲解我们直接用like[i]表示男生i喜欢女生的所在位置编号。
以题中测试样例为例
每个男生i喜欢的女生位置pos[i]分别是:4 2 5 1 3
cns[1]=0+3;
cns[2]=1+1;
cns[3]=0+2;
cns[4]=3+0;
cns[5]=2+0;
后面我们只用到pos,pos[i]为每个男生i喜欢的女生位置。
1、求左边pos[i]比pos[x]大的个数,cnsleft=i-1-sum(pos[x]);
解释:i-1为[1,n]区间的数出现了几个,sum(pos[x])为为[1,pos[x]]区间的数出现了几个,那么i-1-sum(x)为[pos[x]+1,n]区间的数出现了几个。
2、同样,求右边pos[i]比pos[x]小的数,cnsright=sum(pos[x]);(将add(pos[x]放在统计代码后,不然会将自身算进去,如果用sum(pos[x]-1)则无须考虑两句代码的先后位置)
在这里我的处理时,先求右半部分,统计右边,故从n到1遍历
重置树状数组,再求左半部分,统计左边,故从1到n遍历
直接在第二次循环中输出cns即可。(注:如果先求左,再求右,也可,只是要多写一个for循环去输出了)
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N=100005;
int n;
int t[N];//树状数组,t[x]=1,表示x已出现
int pos[N];//女孩位置
int like[N];//心意人
int cns[N];//计数
int lowbit(int x){
return x&-x;
}
void add(int x){//单点修改
for(int i=x;i<=n;i+=lowbit(i)){
t[i]+=1;
}
}
int sum(int x){//求和,sum(x)表示[1,x]区间中的数出现了几个
int ans=0;
while(x){
ans+=t[x];
x-=lowbit(x);
}
return ans;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int fro;
cin>>fro>>like[i];
like[i]-=n;//女孩标号为n+1~2n,且无重复,可以存储她们标号-n的值,节省空间
pos[fro-n]=i;//女孩标号-n,记录每个女孩的位置
}
for(int i=n;i>=1;i--){
like[i]=pos[like[i]];//用每个女生的座位号来代替女生的id号
cns[i]+=sum(like[i]);//统计右边比like[i]小的个数
add(like[i]);//将like[i]加入到树状数组,本行与上一行位置不可调换
}
memset(t,0,sizeof t);//重置树状数组
for(int i=1;i<=n;i++){
cns[i]+=sum(n)-sum(like[i]);//统计左边比like[i]大的个数
add(like[i]);//将like[i]加入到树状数组
cout<<cns[i]<<endl;
}
return 0;
}