永无乡 bzoj-2733 HNOI-2012
题目大意:题目链接。
注释:略。
想法:
它的查询操作非常友善,就是一个联通块内的$k$小值。
故此我们可以考虑每个联通块建一棵权值线段树。
这样的话每次修改采用线段树启发式合并,查询暴力走权值线段树即可。
Code:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100010
using namespace std;
struct Node
{
int ls,rs,size;
Node() {ls=rs=size=0;}
}a[N*50];
int rt[N],fa[N],cnt,val[N],re[N];
int find(int x) {return fa[x]==x?x:fa[x]=find(fa[x]);}
int merge(int x,int y)
{
if(!x||!y) return x|y;
a[x].size+=a[y].size;
a[x].ls=merge(a[x].ls,a[y].ls); a[x].rs=merge(a[x].rs,a[y].rs);
return x;
}
int query(int x,int k,int l,int r)
{
if(l==r) return l;
int ls=a[x].ls,rs=a[x].rs;
int mid=(l+r)>>1;
if(k<=a[ls].size) return query(ls,k,l,mid);
else return query(rs,k-a[ls].size,mid+1,r);
}
int build(int x,int l,int r)
{
// printf("%d %d %d\n",x,l,r);
int p=++cnt;
a[p].size=1;
if(l==r) return p;
int mid=(l+r)>>1;
if(x<=mid) a[p].ls=build(x,l,mid);
else a[p].rs=build(x,mid+1,r);
return p;
}
int main()
{
int n,m; cin >> n >> m ; for(int i=1;i<=n;i++) scanf("%d",&val[i]),re[val[i]]=i,fa[i]=i;
for(int i=1;i<=n;i++) rt[i]=build(val[i],1,n);
// for(int i=1;i<=n;i++) cout << rt[i] << " " ; puts("");
for(int x,y,i=1;i<=m;i++)
{
scanf("%d%d",&x,&y); x=find(x); y=find(y);
if(x!=y)
{
rt[x]=merge(rt[x],rt[y]);
fa[y]=x;
}
}
// for(int i=1;i<=n;i++) printf("%d ",find(i)); puts("");
int q; cin >> q ; while(q--)
{
char opt[10]; int x,y; scanf("%s%d%d",opt,&x,&y);
if(opt[0]=='B')
{
x=find(x); y=find(y);
if(x!=y)
{
rt[x]=merge(rt[x],rt[y]); fa[y]=x;
}
}
else
{
x=find(x);
if(y>a[rt[x]].size) puts("-1");
else printf("%d\n",re[query(rt[x],y,1,n)]);
}
}
return 0;
}
小结:这题是别人好几个月之前写的,当时觉得贼高级现在一看原来是sb题.....