链接: 大富翁
题意:
给一颗树 ,连接 n 个房间 ,n 个大富翁分别住在这 n 个房间,n 人 依次那 走第 k小的砝码 , k 的定义是 每个节点 的每个儿子节点及其对应子树中比当前节点权值大的节点数的最小值( 有点绕)。问每个人取走砝码的重量。
思路:
代码:
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef unsigned long long ll;
const int maxn=1e6+7;
int c[maxn],n,m,T,ans[maxn],ca=1,num[maxn],root,tot,head[maxn];
int le[maxn],ri[maxn],id=1,fa[maxn],op[maxn],anss[maxn],sz[maxn];
int sum[maxn<<2];
struct node{
int l,r,val,id;
}b[maxn];
struct stu{
int id,val;
}a[maxn];
struct ex{
int v,next;
}e[maxn];
bool cmp1(node a,node b){
return a.val>b.val;
}
bool cmp2(stu a,stu b){
return a.val>b.val;
}
void pushup(int rt){
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt){
if(l==r){
sum[rt]=1;
return ;
}
ll m=(l+r)>>1;
build(l,m,rt<<1);
build(m+1,r,rt<<1|1);
pushup(rt);
}
void update1(int i,int c,int l,int r,int rt){
if(l==r) {
sum[rt]=c;
return;
}
ll m=(l+r)>>1;
if(i<=m) update1(i,c,l,m,rt<<1);
else update1(i,c,m+1,r,rt<<1|1);
pushup(rt);
}
int query(int k,int l,int r,int rt){ // 求 第 k 大
if(l==r) return l;
int m=(l+r)>>1;
if(sum[rt<<1]>=k) return query(k,l,m,rt<<1);
else return query(k-sum[rt<<1],m+1,r,rt<<1|1);
}
void add(int u, int v){
e[tot].next=head[u];
e[tot].v=v;
head[u]=tot++;
}
void dfs(int u,int pre){
le[u]=id++;sz[u]=1;
for(int i=head[u];i!=-1;i=e[i].next){
int v=e[i].v;
if(v==pre) continue;
fa[v]=u;
dfs(v,u);
sz[u]+=sz[v];
}
ri[u]=id-1;
}
void dfs1(int u,int pre){
if(sz[u]==1) op[u]=0;
for(int i = head[u]; i != -1; i = e[i].next){
int v=e[i].v;
if(v==pre) continue;
dfs1(v,u);
op[u]=min(op[u],ans[v]);
}
}
int lowbit(int x){
return x&(-x);
}
void update(int x){
while(x<=n){
c[x]+=1;
x+=lowbit(x);
}
}
int getsum(int x){
int sum=0;
while(x>0){
sum+=c[x];
x-=lowbit(x);
}
return sum;
}
int main (){
memset(head,-1,sizeof(head));
memset(op,0x3f3f3f3f,sizeof(op));
scanf("%d",&n);
for(int i = 1,u; i <= n; i ++ ){
scanf("%d",&u);
num[u] = i;
if(u == 1) root = i;
}
for(int i = 0,u,v; i< n-1; i++){
scanf("%d%d",&u,&v);
add(num[u],num[v]);
add(num[v],num[u]);
}
dfs(root,-1); //求dfs序
m=1;
for(int i = 1; i <= n ;i ++){ // 处理需要查询的值,离线操作
if(i==root) continue;
b[m].id=i,
b[m].l=le[i];
b[m].r=ri[i];
b[m++].val=fa[i];
}
m=1;
for(int i = 1; i <= n; i ++){ //存入每个点的权值
if(i==root) continue;
a[m].id=le[i];
a[m++].val=i;
}
sort(a+1,a+m+1,cmp2); //从大到小 插入 查询
sort(b+1,b+m+1,cmp1);
int k=1;
for(int i = 1; i < m; i ++){
while(k < m && a[k].val > b[i].val){
update(a[k].id);
k++;
}
ans[b[i].id]=getsum(b[i].r)-getsum(b[i].l-1);
}
dfs1(root,-1); //得到操作序列
build(1,n,1);
for(int i = 1; i <= n; i ++){ //线段树求 连续区间 第 k 大 相当于二分的思路
anss[i]= query (op[i]+1,1,n,1);
update1(anss[i],0,1,n,1);
}
for(int i=1;i<=n;i++) printf ("%d\n",anss[i]);
}