01Trie树

01Trie树

01字典树(01-trie)是一种特殊的字典树,它的字符集只有 { 0 , 1 } \{0,1\} {0,1},主要用来解决一些关于二进制上的问题,例如异或问题和子集问题等。

求异或和最大

HDU 4825

这题让我们在数组中找出一个数字,使得与给定的数字异或和最大,首先能想到的是线性基,线性基是多个数字的异或组合。这时候就需要用到01Tire树了。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)

struct Tire
{
  int tree[3200000][2];
  int tot;
  void clear()
  {
    for (int i = 0; i < 3200000; i++)
    {
      tree[i][0] = tree[i][1] = 0;
    }
    tot = 0;
  }

  void insert(ll x)
  {
    int curr = 0;
    for (int i = 31; i >= 0; i--)
    {
      int bit = (x >> i) & 1;

      if (tree[curr][bit] == 0)
      {
        tree[curr][bit] = ++tot;
      }
      curr = tree[curr][bit];
    }
  }

  ll query(ll x)
  {
    int curr = 0;
    ll ans = 0;
    for (int i = 31; i >= 0; i--)
    {
      int bit = (x >> i) & 1;

      if (tree[curr][!bit] == 0)
      {
        curr = tree[curr][bit];
        x &= ~(1ll << i);
        ans = (ans << 1 | bit);
      }
      else
      {
        curr = tree[curr][!bit];
        x |= (1ll << i);
        ans = (ans << 1 | !bit);
      }
    }
    return ans;
  }
} tire;

int main()
{
  int T;
  scanf("%d", &T);
  int cnt = 0;
  while (T--)
  {
    tire.clear();
    cnt++;
    printf("Case #%d:\n", cnt);
    int n, m;
    scanf("%d %d", &n, &m);
    while (n--)
    {
      ll x;
      scanf("%lld", &x);
      tire.insert(x);
    }

    while (m--)
    {
      ll x;
      scanf("%lld", &x);
      printf("%lld\n", tire.query(x));
    }
  }
  return 0;
}

解决二进制子集问题

LeetCode 1178 猜字谜

如果你看到了这篇题解,那么你应该对二进制状态压缩并枚举子集+哈希表和Tire树的做法有了一定的认识,那么我们能不能把这两种方法结合起来呢,答案是可以的,使用一种神奇的数据结构——子集查找树。另外,本题又要求加上必须包含首字母的这个条件,所以我们需要魔改一下。

子集查找树

子集查找树类似与Tire树,只不过Tire树处理的是字符串序列,而子集查找树处理的是二进制序列。类比与Tire树,子集查找树中每个节点有两个子节点,分别是0和1,代表了二进制序列中的下一个元素。

查找子集的过程和Tire树的查找过程不完全一致,我在这里使用的是BFS查找方法,即对每一个可能的路径都加入到队列中,不可能的路径直接抛弃(剪枝),在遍历路径的过程中统计子集的个数。

这比枚举子集多了剪枝的过程,其时间应该优于枚举子集。

具体细节请看代码:

// 节点
struct Node
{
    Node * nxt[2]; // 01子节点
    int cnt = 0; // 到这个节点为止有多少个集合
    Node()
    {
        nxt[0] = nullptr;
        nxt[1] = nullptr;
        cnt = 0;
    }
};

struct Subset
{
    Node * root;

    Subset()
    {
        root = new Node();
    }

    // 添加一个集合,过程和Tire树完全一致
    void add(int s)
    {
        Node * curr = root;
        while(s)
        {
            if(curr->nxt[s & 1] == nullptr) curr->nxt[s & 1] = new Node();
            curr = curr->nxt[s & 1];
            s >>= 1;
        }
        curr->cnt++;
    }

    // 查找是tag集合子集的个数,并且第inital位应该是1
    int count(int tag,int inital)
    {
        queue<pair<Node *,int> > que; // BFS队列,pair的第一个元素代表当前路径的端点,第二个元素代表应该处理到第几位了
        que.push(make_pair(root,0));

        int cnt = 0;

        while(!que.empty())
        {
            pair<Node *,int> curr = que.front();
            que.pop();
            // 如果位数大于inital了,说明inital那个位上必是1,直接累计集合数量
            if(curr.second - 1 >= inital)
            {
                cnt+= curr.first->cnt;
            }
            // 如果当前处理的位正好是inital位,那么必须访问nxt[1]子树
            if(curr.second == inital)
            {
                if(curr.first->nxt[1] != nullptr) que.push(make_pair(curr.first->nxt[1],curr.second+1));
            }else if(((tag >> curr.second) & 1) == 1) // 如果当前位是1,nxt[0]子树和nxt[1]子树都可能是tag的子集,都应该加入que
            {
                if(curr.first->nxt[0] != nullptr) que.push(make_pair(curr.first->nxt[0],curr.second+1));
                if(curr.first->nxt[1] != nullptr) que.push(make_pair(curr.first->nxt[1],curr.second+1));
            }else // 如果当前位是0,只能访问nxt[0]子树,nxt[1]子树不是tag的子集
            {
                if(curr.first->nxt[0] != nullptr) que.push(make_pair(curr.first->nxt[0],curr.second+1));
            }
        }
        return cnt;
    }
};

class Solution
{
public:
    // 二进制状态压缩函数
    int compute(string & s)
    {
        int k = 0;
        for(char t : s)
        {
            k |= (1 << (t - 'a'));
        }
        return k;
    }
    vector<int> findNumOfValidWords(vector<string>& words, vector<string>& puzzles)
    {
        Subset ss;
        for(string &s:words) ss.add(compute(s));

        vector<int> ans;

        for(string &s:puzzles)
        {
            ans.push_back(ss.count(compute(s),s[0] - 'a'));
        }
        return ans;
    }
};

最大路径和

P4551

这题很巧妙,以1为根节点创建有根树,任意两个节点之间的路径异或值为 d i s ( i , j ) = d i s ( 1 , j ) ⊕ d i s ( 1 , j ) dis(i,j)=dis(1,j) \oplus dis(1,j) dis(i,j)=dis(1,j)dis(1,j),因为公共路径被抵消了。

我们只需DFS计算出每一个节点的异或值,然后找出两个异或值最大即可。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)

ll XOR[100005];

struct Edge
{
  int to;
  int nxt;
  ll w;
};

struct Graph
{
  Edge e[200005];
  int head[100005];
  int tot;

  void add(int u, int v, ll w)
  {
    tot++;
    e[tot].to = v;
    e[tot].nxt = head[u];
    e[tot].w = w;
    head[u] = tot;
  }
} g;

struct Tire
{
  int tree[3200000][2];
  int tot;

  void insert(ll x)
  {
    int curr = 0;
    for (int i = 32; i >= 0; i--)
    {
      int bit = (x >> i) & 1;

      if (tree[curr][bit] == 0)
      {
        tree[curr][bit] = ++tot;
      }
      curr = tree[curr][bit];
    }
  }

  ll query(ll x)
  {
    int curr = 0;
    ll ans = 0;
    for (int i = 32; i >= 0; i--)
    {
      int bit = (x >> i) & 1;

      if (tree[curr][!bit] == 0)
      {
        curr = tree[curr][bit];
        x &= ~(1ll << i);
        ans = (ans << 1 | bit);
      }
      else
      {
        curr = tree[curr][!bit];
        x |= (1ll << i);
        ans = (ans << 1 | !bit);
      }
    }
    return ans;
  }
} tire;

void dfs(int r, int idx, ll xsum)
{
  XOR[idx] = xsum;
  for (int ne = g.head[idx]; ne != 0; ne = g.e[ne].nxt)
  {
    int v = g.e[ne].to;
    if (v == r)
      continue;
    dfs(idx, v, xsum ^ g.e[ne].w);
  }
}

int main()
{
  int n;
  scanf("%d", &n);
  for (int i = 0; i < n - 1; i++)
  {
    int u, v, w;
    scanf("%d %d %d", &u, &v, &w);
    g.add(u, v, w);
  }
  dfs(1, 1, 0);

  for (int i = 1; i <= n; i++)
  {
    tire.insert(XOR[i]);
  }
  ll mx = 0;
  for (int i = 1; i <= n; i++)
  {
    mx = max(tire.query(XOR[i]) ^ XOR[i], mx);
  }
  printf("%ld", mx);
  return 0;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值