P4647 [IOI2007] sails 船帆 fhq_treap
题意:
分析:
题意:\(m\) 个操作每次操作给出 \(h,k\) 在前 \(h\) 个数字中选出 \(k\) 个 +1,使得操作结束后 \(\sum \frac{x\times (x-1)}{2}\) 最小
易证: 最后使得所有数尽可能小最优 ,若 \(x<y\) 则 \(\frac{x\times (x-1)}{2}+\frac{(y+1)\times y}{2}<\frac{x\times (x+1)}{2}+\frac{(y-1)\times y}{2}\)
所以我们将询问离线下来,按 \(h\) 第一关键字,\(k\) 第二关键字升序排序,需要一个数据结构支持每次选出前 \(h\) 中最小的 \(k\) 个,并且区间加 \(1\)
很显然这可以 \(fhq\) ,注意这个题的 \(fhq\) 以数字大小作为键值,由于我们排序,所以不需要考虑位置带来的影响,避免了 \(fhq\) 无法同时维护 序列 和 大小 的缺点
对于每一次查询,直接裂出大小前 \(k\) 小之后,打上加法标记塞回去,就会得到 \(14\) 分的好成绩/kk
我们发现可能存在一种情况,第 \(k\) 小的值没有被取完,即 \([x,k]\) 和 \((k,y]\) 两段区间大小一样,但我们裂开的区间里面只包含了前一段,这样打上加法标记之后合并,\([x,k]\) 这一段就会比 \((k,y]\) 这一段大 \(1\) ,不满足二叉搜索树的性质了,所以我们每次需要将区间裂开成四段 分别是 \([1,x],(x,y-(k-x)](y-(k-x),y],(y,n]\) 我们需要给第 \(1,3\) 段打上加法标记之后塞回去,所以我们发现我们的 \(fhq\) 需要支持按 \(siz\) 分裂 (取出大小最小的前\(k\) 个)还需要支持按 \(val\) 分裂,(取出大/小于 第 \(k\) 小的区间)
我直接裂开,第一次看见需要两种 \(split\) 的 \(fhq\)
代码:
#include<bits/stdc++.h>
#define mk(x,y) make_pair(x,y)
#define fir first
#define sec second
#define pii pair<int,int>
#define lc son[rt][0]
#define rc son[rt][1]
using namespace std;
namespace zzc
{
int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const int maxn = 1e5+5;
int n;
long long ans;
pii q[maxn];
namespace fhq_treap
{
int val[maxn],son[maxn][2],rnd[maxn],siz[maxn],tag[maxn];
int cnt,rt;
int new_node()
{
int u=++cnt;
siz[u]=1;val[u]=0;tag[u]=0;
rnd[u]=rand();
return u;
}
void pushup(int rt)
{
siz[rt]=siz[lc]+siz[rc]+1;
}
void pushdown(int rt)
{
if(tag[rt])
{
tag[lc]+=tag[rt];
tag[rc]+=tag[rt];
val[lc]+=tag[rt];
val[rc]+=tag[rt];
tag[rt]=0;
}
}
int merge(int x,int y)
{
if(!x||!y) return x|y;
pushdown(x);pushdown(y);
if(rnd[x]<rnd[y])
{
son[x][1]=merge(son[x][1],y);
pushup(x);
return x;
}
else
{
son[y][0]=merge(x,son[y][0]);
pushup(y);
return y;
}
}
void split1(int tmp,int k,int &u,int &v)
{
if(!tmp)
{
u=0;v=0;
return ;
}
pushdown(tmp);
if(siz[son[tmp][0]]<k)
{
u=tmp;
split1(son[u][1],k-siz[son[tmp][0]]-1,son[u][1],v);
pushup(u);
}
else
{
v=tmp;
split1(son[v][0],k,u,son[v][0]);
pushup(v);
}
}
void split2(int tmp,int k,int &u,int &v)
{
if(!tmp)
{
u=0;v=0;
return ;
}
pushdown(tmp);
if(val[tmp]<=k)
{
u=tmp;
split2(son[u][1],k,son[u][1],v);
pushup(u);
}
else
{
v=tmp;
split2(son[v][0],k,u,son[v][0]);
pushup(v);
}
}
void update(int k)
{
int x,y,z,w,tmp,num1,num2;
split1(rt,k,x,y);
tmp=x;
while(son[tmp][1]) tmp=son[tmp][1];
tmp=val[tmp];
rt=merge(x,y);
split2(rt,tmp-1,x,y);
num1=siz[x];
rt=merge(x,y);
split2(rt,tmp,x,y);
num2=siz[x];
rt=merge(x,y);
split1(rt,num1,x,y);
split1(y,num2-k,y,z);
split1(z,k-num1,z,w);
val[z]+=1;
val[x]+=1;
tag[z]+=1;
tag[x]+=1;
rt=merge(merge(merge(x,y),z),w);
}
void calc()
{
int x=rt,y,z;
for(int i=1;i<=cnt;i++)
{
split1(x,1,y,z);
ans+=1ll*val[y]*(val[y]-1)/2;
x=z;
}
}
void insert(int k)
{
if(!k) return ;
for(int i=1;i<=k;i++) rt=merge(new_node(),rt);
}
}
using namespace fhq_treap;
void work()
{
srand(time(0));
int x,y;
n=read();
for(int i=1;i<=n;i++)
{
x=read();y=read();
q[i]=mk(x,y);
}
sort(q+1,q+n+1);
int tmp=0;
for(int i=1;i<=n;i++)
{
insert(q[i].fir-tmp);
update(q[i].sec);
tmp=q[i].fir;
}
calc();
printf("%lld\n",ans);
}
}
int main()
{
zzc::work();
return 0;
}