01Trie树
01字典树(01-trie)是一种特殊的字典树,它的字符集只有 { 0 , 1 } \{0,1\} {0,1},主要用来解决一些关于二进制上的问题,例如异或问题和子集问题等。
求异或和最大
这题让我们在数组中找出一个数字,使得与给定的数字异或和最大,首先能想到的是线性基,线性基是多个数字的异或组合。这时候就需要用到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;
}
解决二进制子集问题
如果你看到了这篇题解,那么你应该对二进制状态压缩并枚举子集+哈希表和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;
}
};
最大路径和
这题很巧妙,以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;
}