Pandaria(Kruskal重构树+线段树合并)

题意 是 有n个花园 一个花园内所有的花的颜色都是一样的 有很多种不同的颜色  花园到花园之间有路,走不同的路有不同的代价
   如果选一个点作为起点 只走小于等于w的路  可以经过的这些花园里  那种颜色最多  多组询问 强制在线
   
解法   对于这个影响这个答案有两个因素 不可以把所有答案求出  一个一个求的话复杂度太高  因为强制在线所以我们需要先预处理一部分数据 然后根据预处理的数据 再进行求解 答案, 对于这个题面 很明显可以想到 先把边权最小的连起来 然后是次最大 然后再大点  依次
明显就是一个 求最小生成树的过程  每次链接一条边的时候 我们都可以 从当前局面 计算出一个答案 明确可以知道 存在一组询问的答案都是这个  用并差集维护 集合   当我们把两个集合 连接起来的时候 就把这两个 集合 链接到一个 点上  用这个点来代码 这个两个集合合并后的状态 一共有n个点 所以我们需要建立n-1个点   按照最小生成树建树的顺序 可以获得一个二叉树  对于这个二叉树 每一个节点都可以求一个答案  这些答案 可以回答所有询问,   这些节点一共是 2n-1个(前n个几乎不用考虑 就是本身)  我们只需要先把这n-1个结点的答案预处理出来就可以了  对于之后的询问 我们就可以使用 倍增 直接找到 从x点最高可以到的位置 从而得出答案   
对于那n-1的节点的答案维护 要使用线段树合并(合并树 最初的线段树要使用动态开点线段树 动态开点的话也就是一共树就是一条链 log2e5 不会超过20个  n个线段树的话直接开个n*20的内存就可以了)

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 2e5+7;
int color[maxn],f[maxn];
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void init(int N){for(int i=1;i<=N;i++)f[i]=i;}
struct edge{int x,y,l;}E[maxn];
bool com(edge a,edge b){return a.l<b.l;}
struct Node{int l,r,mx;}node[maxn*20];
int N,M,tot,root[maxn];
void pushup(int rt){node[rt].mx=max(node[node[rt].l].mx,node[node[rt].r].mx);}
void bulit(int l,int r,int &rt,int pos)
{
    node[++tot]=(Node){0,0,0};rt=tot;
    if(l==r){node[rt].mx=1;return;}
    int m=(l+r)>>1;
    if(pos<=m)bulit(l,m,node[rt].l,pos);
    else bulit(m+1,r,node[rt].r,pos);
    pushup(rt);
}
void merge(int l,int r,int&x,int y)
{
    if(!x||!y){ x= x+y;return ;}
    if(l==r)node[x].mx+=node[y].mx;
    else{
        int m=(l+r)>>1;
        merge(l,m,node[x].l,node[y].l);
        merge(m+1,r,node[x].r,node[y].r);
        pushup(x);
    }
}
int ans[maxn],limt[maxn];
int getans(int l,int r,int x){
    if(l==r)return l;
    int m=(l+r)>>1;
    if(node[x].mx==node[node[x].l].mx)return getans(l,m,node[x].l);
    return getans(m+1,r,node[x].r);
}
int p[maxn][20];
int main()
{
    int T,cases=0,Q;
    scanf("%d",&T);
    while(T--)
    {
        memset(p,0,sizeof(p));
        scanf("%d%d",&N,&M);
        for(int i=1;i<=N;i++)scanf("%d",color+i);
        for(int i=0;i<M;i++)scanf("%d%d%d",&E[i].x,&E[i].y,&E[i].l);
        sort(E,E+M,com);
        init(N*2+1);tot=0;int cnt=N;
        for(int i=1;i<=N;i++)bulit(1,N,root[i],color[i]),ans[i]=color[i];
        for(int i=0;i<M;i++)
        {
            int x=find(E[i].x),y=find(E[i].y);
            if(x==y)continue;
            merge(1,N,root[x],root[y]);
            root[++cnt]=root[x];
            ans[cnt]=getans(1,N,root[x]);
            limt[cnt]=E[i].l;
            p[x][0]=p[y][0]=cnt;
            f[y]=f[x]=cnt;
        }
        for(int i=1;i<20;i++)
            for(int j=1;j<=cnt;j++)
                p[j][i]=p[p[j][i-1]][i-1];
        scanf("%d",&Q);
        printf("Case #%d:\n",++cases);
        int x,w,last=0;
        while(Q--){
            scanf("%d%d",&x,&w);
            x^=last;w^=last;
            for(int i=19;i>=0;i--)if(limt[p[x][i]]<=w&&p[x][i])x=p[x][i];
            printf("%d\n",last=ans[x]);
        }
    }
    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/DWVictor/p/10283150.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值