[BZOJ3295][CQOI2011]动态逆序对(cdq分治+树状数组)
Solution
此题可以十分简单粗暴地套用树状数组套主席树的模板。
或者:cdq分治。
此题中,原先给出一个数列,之后会删除一些数。但是,呃,删除操作好像有点儿麻烦。反正允许离线,那就当作是初始给出一些数,先把这些数加入序列,之后会再添加一些数。也就是全部反过来算。
可以把每次的答案分成两个部分:原先存在的逆序对+加入这个数新产生的逆序对,那么每次只要算出当前新产生的逆序对,最后算一遍前缀和即可。
加入这个数新产生的逆序对也可以分成两个部分:位置靠前且值比它大的,位置靠后且比它小的。那么总共有三种操作,(1)往序列的某个位置加入一个数,(2)查询比某个数位置靠前且值比它大的数量,(3)查询比某个数位置靠后且值比它小的数量。显然,根据给出的删除顺序,可以得出每个操作的先后顺序,即每个操作的时间。要在cdq分治之前,要先按操作的时间从小到大排序。
在cdq分治中,每次会合并左右两个序列。在合并的时候,如果要查询位置靠前且比它大的,就要先执行位置靠前的操作,位置相同的操作,先执行加入操作,树状数组维护的是值,也就是说cdq分治结束以后,所有操作将按位置从小到大顺序排好。这样不方便查询位置靠后且比它小的。
那么,考虑把所有的操作分成互不影响的两组操作,第一组:所有的操作(1)和操作(2)。第二组:所有的操作(1)和操作(3)。两组操作分别先按时间从小到大排序,第二组操作在cdq分治的时候,先执行值小的操作,树状数组维护的是位置。
最后把两组操作所得结果累计,就得到了每次答案的加入这个数新产生的逆序对这一部分。
代码建议自己完成,实在不行的话,下面那个代码凑合着看吧QAQ
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define lowbit(x) (x&(-x))
inline int getint()//读入优化
{
char ch;
int res=0;
while(ch=getchar(),ch<'0'||ch>'9');
res=ch-48;
while(ch=getchar(),ch>='0'&&ch<='9')
res=res*10+ch-48;
return res;
}
const int e=3e5+5;
struct point
{
int id,v,t,opt,k;
}a[e],t[e],b[e];//id是位置,v是数值。k其实没什么用,然而不知道为什么写进去了。a,b为两组操作
int n,m,real[e],c[e],cnt,num;
bool vis[e];
long long ans[e];//逆序对总数可能超过int范围
inline bool cmp(const point &a,const point &b)//排序
{
return a.t<b.t||(a.t==b.t&&a.opt<b.opt);//t是时间,opt=1为加入操作,opt=2为询问操作
}
inline void init(int x)//树状数组清零
{
while(x<=n)
{
c[x]=0;
x+=lowbit(x);
}
}
inline void add(int x,int d)//树状数组单点修改
{
while(x<=n)
{
c[x]+=d;
x+=lowbit(x);
}
}
inline int query(int x)//树状数组前缀和查询
{
int res=0;
while(x)
{
res+=c[x];
x-=lowbit(x);
}
return res;
}
inline void solve(int l,int r)//第一组分治
{
if(l==r)return;
int mid=(l+r)/2,id1=l,id2,i;
id2=mid+1;
solve(l,mid);
solve(mid+1,r);
for(i=l;i<=r;i++)
if(id2>r||id1<=mid&&a[id1].id<=a[id2].id)//先执行位置靠前的
{
t[i]=a[id1++];
if(t[i].opt==1)
add(t[i].v,1);
}
else
{
t[i]=a[id2++];
if(t[i].opt==2)
ans[t[i].k]+=(long long)query(n)-(long long)query(t[i].v);
//查询值比它大的数量
}
for(i=l;i<=r;i++)
{
a[i]=t[i];
init(a[i].v);
}
}
inline void solve2(int l,int r)//第二组分治
{
if(l==r)return;
int mid=(l+r)/2,id1=l,id2,i;
id2=mid+1;
solve2(l,mid);
solve2(mid+1,r);
for(i=l;i<=r;i++)
if(id2>r||id1<=mid&&b[id1].v<=b[id2].v)//先执行值较小的
{
t[i]=b[id1++];
if(t[i].opt==1)
add(t[i].id,1);
}
else
{
t[i]=b[id2++];
if(t[i].opt==2)
ans[t[i].k]+=(long long)query(n)-(long long)query(t[i].id);
//查询位置比它靠后的数量
}
for(i=l;i<=r;i++)
{
b[i]=t[i];
init(b[i].id);
}
}
int main()
{
int i,x,y;
n=getint();
m=getint();
for(i=1;i<=n;i++)
{
x=getint();
real[x]=i;//x的位置为i
}
for(i=1;i<=m;i++)//创建操作
{
x=getint();
y=real[x];//y为x的位置
vis[x]=true;
a[++cnt]=(point){y,x,n-i+1,1,0};
a[++cnt]=(point){y,x,n-i+1,2,n-i+1};
b[++num]=(point){y,x,n-i+1,1,0};
b[++num]=(point){y,x,n-i+1,2,n-i+1};
}
int ti=0;
for(i=1;i<=n;i++)//创建操作
if(!vis[i])
{
a[++cnt]=(point){real[i],i,++ti,1,0};
a[++cnt]=(point){real[i],i,ti,2,ti};
b[++num]=(point){real[i],i,ti,1,0};
b[++num]=(point){real[i],i,ti,2,ti};
}
sort(a+1,a+cnt+1,cmp);//排序
sort(b+1,b+num+1,cmp);
solve(1,cnt);
solve2(1,num);
for(i=2;i<=n;i++)//做一遍前缀和
ans[i]+=ans[i-1];
for(i=n;i>=n-m+1;i--)
printf("%lld\n",ans[i]);
return 0;
}