可怜的ljb
Time limit1000 ms
题目链接https://csustacm.fun/problem/1090
Description
Ljb学长最爱出题了!只不过很可惜每次比赛,大家都能准确的避开ljb的题目,ljb欲哭无泪。这次ljb希望能有人写出自己的题目,所以他开始研究大佬们的出题顺序。
他看了一场cf的排名。一共有n个人,编号为从1到n的整数。
第一行是第1小时结束时的rank从第1名到第n名的同学的编号,第二行是第2小时结束时的rank从第1名到第n名的同学的编号。。
Ljb学长想要知道这两个小时之间有多少对同学的排名的相对顺序交换了。
Input
第一行是T(1<=T<=10)表示有T组数据。
后面每组样例的第一行一个数n(1<=n<=100000)表示有nn个人。
接下来的一行有n个数表示第一小时结束时从第1名到第n名的同学的编号。
再接下来的一行有n个数表示第二小时结束时从第1名到第n名的同学的编号。
保证T组数据n的和不超过700000。
Output
每组数据输出一个整数,表示答案。
Sample Input 1
2
5
5 4 2 1 3
2 5 4 1 3
4
1 2 3 4
1 2 4 3
Sample Output 1
2
1
Hint
第一组样例解释:
有两对同学的相对顺序改变了,分别是:(5,2),(4,2)。
因为第一轮5排在2前面,但是第二轮2排在5前面,(4,2)同理,故答案是2.
第二组样例解释:
组数据有一对同学的相对顺序改变了,分别是:(3,4)。
因为第一轮3排在4前面,但是第二轮4排在3前面,而且仅有这一对,故答案是1.
这道题目只要把第一次输入的数据标记一下为1~n就可以直接套用逆序对的板子了
至于逆序对的求法:先建一颗空树,对于每次输入的数据,查找该数据~n的范围,询问此时有多少数存在,此时的ans为当前点的逆序对个数
#include <cstdio>
#define ll long long
struct st
{
int l,r;
ll w;
}tree[100050*4];
ll sum=0;
int a,vis[100050];
void build(int now,int l,int r);
ll ask(int l,int r,int now);
void change(int s,int now);
int main()
{
int n,t,ts;
scanf ("%d",&t);
while (t--){
scanf ("%d",&n);
for (int i=1; i<=n; i++){
scanf ("%d",&ts);
vis[ts]=i;
}
build(1,1,n);
sum=0;
for (int i=1; i<=n; i++){
scanf ("%d",&a);
sum+=ask(vis[a],n,1); //加上该点的逆序对数
change(vis[a],1); //更新该点的数据
}
printf ("%lld\n",sum);
}
return 0;
}
void build(int now,int l,int r)
{
tree[now].l=l,tree[now].r=r;
if (l==r){
tree[now].w=0;
return;
}
int mid=(l+r)>>1;
build(now*2,l,mid);
build(now*2+1,mid+1,r);
tree[now].w=tree[now*2].w+tree[now*2+1].w;
}
ll ask(int l,int r,int now)
{
if (tree[now].l>=l && tree[now].r<=r){
return tree[now].w;
}
int mid=(tree[now].r+tree[now].l)>>1;
ll ans=0;
if (l<=mid) ans+=ask(l,r,now*2);
if (r>mid) ans+=ask(l,r,now*2+1);
return ans;
}
void change(int s,int now)
{
if (tree[now].l>=s && tree[now].r<=s){
tree[now].w=1; //该点存在
return;
}
int mid=(tree[now].r+tree[now].l)>>1;
if (s<=mid) change(s,now*2);
if (s>mid) change(s,now*2+1);
tree[now].w=tree[now*2].w+tree[now*2+1].w;
}