牛客国庆集训派对Day1 D-Love Live! (启发式合并+01字典树)

版权声明:Why is everything so heavy? https://blog.csdn.net/lzc504603913/article/details/82931895

链接:https://www.nowcoder.com/acm/contest/201/D
来源:牛客网
 

题目描述

因为招生办的招生政策变化,Otonokizaka Academy的ACM-ICPC team面临废队危机。Honoka Kosaka,Kotori Minami,Umi Sonoda等人决定成为偶像来吸引更多的学生参加ICPC。
Honoka决定选取一些动作来编舞。我们把所有可以选择的动作用一棵 n 个点的树上的边表示,其中树的定义是无环的无向联通图。树上的每条边有一个边权 w(1 ≤ w < n),且所有边的边权是互不相同的。如果两条边没有公共节点,就代表它们对应的动作差异很大,没有办法连续做出。又因为每个动作只能在

舞蹈中出现一次,所以能组成一支舞蹈的一套动作一定对应着树上的一条简单路径。

此外,舞蹈的优美度定义为其路径上所有边的边权异或和,难度定义为路径上所有边的边权最大值。
Honoka想知道对于[1, n) 的每种难度,最优美的舞蹈的优美度是多少。

输入描述:

输入第一行一个正整数 n(2 ≤ n ≤ 105)。
接下来 n-1 行,每行三个正整数 u,v,w(1≤ u,v ≤ n, 1≤ w < n) 表示点 u 和点 v 之间有一条边权为 w 的边。
保证输入的图可以构成一棵树,且所有边的边权互不相同。

输出描述:

一行 n-1 个整数,第 i 个数表示所有难度为 i 的舞蹈中最大的优美度。

示例1

输入

复制

7
1 2 4
1 3 3
2 4 1
2 5 2
3 6 5
3 7 6

输出

复制

1 3 3 7 6 6

解题思路:异或相关肯定想到字典树。对于每一条边,我们只关心周围比他小的边。考虑暴力,对于每一条边,我们把周围比他小的边都单独拿出来重新建树,重新建树之后怎么确定最大异或和呢,答案肯定与边两边的子树有关。我们记录下边两边的子树的所有节点到他的根(根就是两个顶点)的异或和,然后就可以通过两个for暴力计算出答案了。这一步可以优化一下,我们先预处理出一边的点到他的根异或和,然后插入到一颗字典树里,然后另一边直接在字典树中查询即可,这样复杂度变成了一个for了。毫无疑问我们每次遍历,肯定是遍历小的那个子树好,所以这里启发式一下。但是我们重新建树需要一个dfs,因此总的复杂度还是不能接受。考虑优化,我们每次都从最小的边重新建树,这样下一个大小的边如果跟之前的树联通,直接插入边即可。这样一共只会插入边N次。然后用并查集维护下每一条边两边的根即可。

这里用遍历一个G即可模拟dfs操作,学到了。

#include<iostream>
#include<algorithm>
#include<math.h>
#include<queue>
#include<string>
#include<vector>
#include<bitset>
using namespace std;
const int MAXN=100006;

struct edge{
    int u,v,w;
}e[MAXN];
bool cmp(const edge &a,const edge &b){
    return a.w<b.w;
}
vector<int> G[MAXN];//记录一个点会深搜到的点,按照dfs序排,所以遍历一遍等于深搜了一次。
int sz[MAXN];//记录子树大小,用于启发式
int V[MAXN];//每一个点到他的并查集的根的异或和(用于优化深搜)

//并查集
int fa[MAXN];
int find(int x){
    return x==fa[x]?x:fa[x]=find(fa[x]);
}

/*******01字典树部分********/
int ch[MAXN*100][2];
int tot=0;
int root[MAXN];//新建N个字典树
void insert(int u,int x)//向根为u的字典树插入x
{
    for(int i=18;i>=0;i--)
    {
        int id=(x>>i)&1;
        if(!ch[u][id])
            ch[u][id]=++tot;
        u=ch[u][id];
    }
}
int query(int u,int x)//查询根为u的字典树与x的异或最大值
{
    int res=0;
    for(int i=18;i>=0;i--)
    {
        int id=(x>>i)&1;
        if(ch[u][id^1])
        {
            u=ch[u][id^1];
            res|=(1<<i);
        }
        else
            u=ch[u][id];
    }
    return res;
}
/*****01字典树部分结束**********/


int main(){

    int N;
    scanf("%d",&N);
    for(int i=1;i<N;i++)
        scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
    sort(e+1,e+N,cmp);

    for(int i=1;i<=N;i++){
        fa[i]=i;
        sz[i]=1;
        G[i].emplace_back(i);
        root[i]=++tot;
        insert(root[i],0);//01字典树的坑,必须要插入一个0
    }


    for(int j=1;j<N;j++){
        int x=e[j].u;
        int y=e[j].v;
        int w=e[j].w;

        int fx=find(x);
        int fy=find(y);

        //启发式
        if(sz[fx]>sz[fy]){
            swap(x,y);
            swap(fx,fy);
        }
        w=V[x]^V[y]^w;//由于使用了并查集合并,所以这里有个坑。但直接使用深搜没有这个坑

        int ans=0;

        for(int i=0;i<G[fx].size();i++)//暴力深搜小的那个,查询跟大的那个的异或和的最大值
            ans=max(ans,query(root[fy],V[G[fx][i]]^w));
        
        for(int i=0;i<G[fx].size();i++)//更新大的那个
        {
            G[fy].emplace_back(G[fx][i]);
            V[G[fx][i]]^=w;
            insert(root[fy],V[G[fx][i]]);
        }
        fa[fx]=fy;
        sz[fy]+=sz[fx];

        printf("%d ",ans);
    }

    return 0;
}








猜你喜欢

转载自blog.csdn.net/lzc504603913/article/details/82931895