HDU 4776 Ants(字典树+优先队列+思维题)

来源:互联网 发布:越狱重启后软件消失 编辑:程序博客网 时间:2024/06/06 03:26

Problem Description
  There are some apple trees in a farm. An apple tree can be described as a connected graph which has n nodes and n-1 edges. The apples are the nodes and the branches are the edges. Every edge is assigned a value denoting the length of the branch.
  Now in the farm come a lot of ants, which are going to enjoy the delicious apples. The ants climb the tree one by one. Every ant would choose a node as the starting node and another node as the ending node, then it would crawl alone the unique path from the starting node to the ending node. The distance between two nodes is defined as the XOR sum of lengths of all the edges in the unique path between them. Every ant wants to crawl along such a path which the distance is as large as possible. But two ants cannot crawl from the same starting node to the same ending node. You should calculate the distance which the k-th ant crawled.
  Note that the starting node and the ending node cannot be the same for an ant.

Input
  The input consists of several test case.
  For each test case, the first line contain an integer n denoting the number of nodes.
  The next n-1 lines each contains three integers x,y,z, denoting that there exists an edge between node x and node y and its length is z. The nodes are numbered from 1 to n.
  The next line contain a integer m denoting the number of queries.
  In the next m lines, each line contains an integer k denoting that you need to calculate the distance of the k-th ant.
  The input ends with n = 0.
  (1 <= n, m <= 100000, 1 <= x, y <= n, 0 <= z <= 10^18, 1 <= k <= 200000)

Output
  For each query, output the answer. If such path does not exist, just output -1.

Sample Input
3
1 2 2
3 2 3
3
1
2
5
5
1 3 7
2 1 3
4 3 6
5 3 1
3
1
8
1000
0

Sample Output
3
3
1
7
6
-1
Hint
  In the first test case, the first ant may crawl from node 2 to node 3, and the second ant may crawl from node 3 to node 2, and the 5-th ant may crawl from node 1 to node 3.
  The distance of the 5-th ant can be calculated by 2 xor 3 = 1.

大致题意:给你一颗有n个节点,n-1条边的树,每条边都有个权值ai,有一些蚂蚁在树上爬,一个蚂蚁会从一个节点爬到另一个节点,收益即为所经过路径的异或和,两个蚂蚁不能选择相同的起点和终点,每个蚂蚁都想自己的收益最大,接下来有m个询问,告诉你某个蚂蚁是第几只开始爬的,问你该蚂蚁的最大收益是多少,如果该蚂蚁无法行动则输出-1。
简单的来说,有m次查询,每次询问你所有路径的异或和中第ki大的值是多少。

思路:首先我们任选一个点作为根节点,然后预处理出其他点到该根节点的路径异或和,保存到数组a[]中,那么任意两点i,j之间的路径异或和的值就等于a[i]*a[j] (如果i为根节点那么a[i]=0)。那么此时问题就转化成了给你n个数(假设节点数为n),让你从中任选两个进行异或,所得到的值第k大为多少。需要注意的是选择a[i],a[j]和选择a[j],a[i]异或所得到的值算两种情况。
观察到k<=2e5,所以我们不妨将前2e5大的值全部先预处理出来,然后查询的时候O(1)。
首先将求出来的这n个数a[i]全部扔进字典树中,然后枚举起点i,求出以i为起点的最大路径异或和,然后用Node记录下该节点i的编号,第k大,第k大的值,扔进优先队列里(第k大的值越大优先度越高),那么显然第一大的值即为此时优先队列的top,然后我们记录下 top的信息,然后pop,接下来再以此时top中的节点编号idx为起点,查询以idx为起点第k+1大路径异或和,然后用Node记录下该节点idx的编号,第k+1大,第k+1大的值,扔进优先队列里。。。重复上面的的操作,直到将前2e5大的值全部找出,或者队列为空。
为什么这样做可以呢,可以这样想,假如此时以i号节点为起点的第k大路径异或和在该队列中是最大的,那么下一个次大的值要么是以i号节点为起点的第k+1大的路径异或和,或者是已经在队列中的其它值,所以我们只需将以i号节点为起点的第k+1大的路径异或和求出来记录下扔进优先队列中即可。
然后想要查询一个数与一个数组中的某个数异或所得到第k大的值,我们只需要记录下字典树中的每个节点所经过的次数即可,具体实现看代码。
n=1时需要特判下答案,
多注意细节多注意细节多注意细节(重要的事说三遍)

代码如下

#include<cstdio>#include<cstring>#include<algorithm>#include<queue>using namespace std;typedef long long LL;const int N = 1e5 + 5;struct Node{    int idx;//节点编号     int k;//第k大     LL val;//以该节点为起始点的路径异或和第k大的值     Node(int a=0,int b=0,LL c=0):idx(a),k(b),val(c){}    bool operator <(const Node &D) const     {        return val<D.val;    }};priority_queue<Node> que;//优先队列 struct Trie{    int ch[2];    int size;//记录经过该节点的次数 }T[62*N];struct Edge{    int to;    LL cost;// edge[i].to表示编号为i的边所连接下个点 ,edge[i].cost表示编号为i的边 的权值     int next;//edge[i].next表示与编号为i的边同起点的上一条边的编号}edge[2*N];int head[N],wei,tol,tot;LL a[N];//保存其他点到根节点的路径的异或和 LL Ans[2*N];//保存答案,即第k大的值为多少 void addedge(int u,int v,LL w){    edge[tol].to=v;    edge[tol].cost=w;    edge[tol].next=head[u];    head[u]=tol++;}void dfs(int u,int pre,LL val)//此时节点,前驱节点,异或值{    a[u]=val;    for(int i=head[u];i!=-1;i=edge[i].next)    {        int to=edge[i].to;        if(to!=pre)        {            dfs(to,u,val^edge[i].cost);        }    }}void add(LL x){    int p=0;    for(int i=60;i>=0;i--)    {        int k=(x>>i)&1;        if(!T[p].ch[k]) T[p].ch[k]=++tot;        p=T[p].ch[k];        T[p].size++;       }}LL query(LL x,int f)//求x与这个n个数中某个数异或的第f大的值是多少{    LL ans=0;    int p=0;    for(int i=60;i>=0;i--)    {        int k=(x>>i)&1;        if(T[T[p].ch[k^1]].size>=f)//如果k^1处节点经过的次数大于f,说明第f大的数在k^1这侧             ans^=(1LL<<i),p=T[p].ch[k^1];//这里记得用1LL,不然会爆精度,wa了好多次。。        else //否则我们找的第f大的数在另一侧,此时我们需要找的是第f-T[T[p].ch[k^1]].size大的值            f-=T[T[p].ch[k^1]].size,p=T[p].ch[k];    }    return ans;}void init(){    wei=1;    tol=0;    tot=0;    memset(head,-1,sizeof(head));    memset(T,0,sizeof(T));    while(!que.empty())     que.pop();}int main(){    int n;    while(scanf("%d",&n)&&n)    {        init();        for(int i=1;i<n;i++)        {            int u,v;            LL w;            scanf("%d%d%lld",&u,&v,&w);            addedge(u,v,w);addedge(v,u,w);        }           dfs(1,1,0);//求出其他点到根节点的路径异或和 ,假设1为根节点        for(int i=1;i<=n;i++) add(a[i]);//用这n个数建立字典树        for(int i=1;i<=n;i++)        {            LL res = query(a[i],1);            que.push(Node(i,1,res));        }        int cnt = 1;//表示第k大        while(!que.empty()&&cnt<=200000)        {            Node u = que.top();que.pop();            Ans[cnt]=u.val;            cnt++;            if(u.k<n-1)//最多只能有第n-1大                que.push(Node(u.idx,u.k+1,query(a[u.idx],u.k+1)));        }        cnt--;        int m;        scanf("%d",&m);        while(m--)        {            int x;            scanf("%d",&x);            printf("%lld\n",(x>cnt||n==1)?-1:Ans[x]);        }    }    return 0;}