每日推荐–详解主席树
题目列表1
题目列表2
题目3
1 HDU 1251
统计难题
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 131070/65535 K (Java/Others)
Total Submission(s): 52206 Accepted Submission(s): 18295
Problem Description
Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀).
Input
输入数据的第一部分是一张单词表,每行一个单词,单词的长度不超过10,它们代表的是老师交给Ignatius统计的单词,一个空行代表单词表的结束.第二部分是一连串的提问,每行一个提问,每个提问都是一个字符串.
注意:本题只有一组测试数据,处理到文件结束.
Output
对于每个提问,给出以该字符串为前缀的单词的数量.
Sample Input
banana
band
bee
absolute
acm
ba
b
band
abc
Sample Output
2
3
1
0
Author
Ignatius.L
Recommend
Ignatius.L
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
char word[200];
const int maxn = 1e6+7;
int trie[maxn][30],num[maxn],cnt = 1;
void Trie_insert(char* word)
{
int p = 0;
for(int i=0;word[i];i++)
{
if(trie[p][word[i]-'a']==0)
trie[p][word[i]-'a'] = cnt++;
p = trie[p][word[i]-'a'];
num[p]++;
}
}
int Trie_search(char* word)
{
int p = 0;
for(int i=0;word[i];i++)
{
int t = word[i]-'a';
if(trie[p][t]==0)return 0;
p = trie[p][t];
}
return num[p];
}
int main()
{
//freopen("in.txt","r",stdin);
while(gets(word))
{
if(word[0]==NULL)
{
break;
}
Trie_insert(word);
}
while(~scanf("%s",word))
{
printf("%d\n",Trie_search(word));
}
return 0;
}
// https://blog.youkuaiyun.com/williamsun0122/article/details/71056547
2 . 参考博客
2017HZAU现场赛H-MathematicalGame
题目链接:http://acm.hzau.edu.cn/problem.php?id=1206
题意:有T组样例,每组样例给n个数,a1…an(n<=1000000)。求这n个数中最大异或和值的区间。有多个答案区间按字典序输出。
题解:把1-n的所有前缀异或和插入01字典树,然后按区间异或的性质扫一遍就可以了。注意一下区间按字典序输出。
注意数组大小,会卡初始化的常数
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+7;
int trie[maxn*32][2];
int idx[maxn*32],cnt =0;
int ans,l,r;
void Trie_insert(int u,int x)
{
int p = 0;
for(int i=31; i>=0; i--)
{
int t = (x>>i)&1;
if(!trie[p][t])trie[p][t] = cnt++;
p = trie[p][t];
}
if(u<idx[p])idx[p] = u;//p 为记录下前缀和的右区间,即答案的左区间
}
void init()
{
memset(trie,0,sizeof trie);
cnt = 1;
ans =0 ;
l = 0;
r =0 ;
memset(idx,0x3f3f3f3f,sizeof idx);
}
void Trie_query(int u,int x)
{
int p = 0;
int m = 0;
for(int i=31; i>=0; i--)
{
int t = (x>>i)&1;
if(trie[p][t^1])
{
p = trie[p][t^1];
m+=(1<<i);
}
else
{
p = trie[p][t];
m+=(0<<i);
}
}
if(m>ans)
{
l = idx[p];
r = u;
ans = m;
}
else if(m==ans)
{
ans = m;
if(idx[p]<l)
{
l = idx[p];
r = u;
}
else if(idx[p]==l&&u<r)
{
r = u;
}
}
}
int main()
{
//freopen("in.txt","r",stdin);
int T,n,tmp;
scanf("%d",&T);
int id = 0;
while(T--)
{
int y = 0;
scanf("%d",&n);
init();
Trie_insert(0,0);
for(int i=1; i<=n; i++)
{
scanf("%d",&tmp);
y^=tmp;
Trie_insert(i,y);
Trie_query(i,y);
}
printf("Case #%d:\n%d %d\n",++id,l+1,r);
}
return 0;
}
3。Xor Sum HDU - 4825
01字典树裸题
Zeus 和 Prometheus 做了一个游戏,Prometheus 给 Zeus 一个集合,集合中包含了N个正整数,随后 Prometheus 将向 Zeus 发起M次询问,每次询问中包含一个正整数 S ,之后 Zeus 需要在集合当中找出一个正整数 K ,使得 K 与 S 的异或结果最大。Prometheus 为了让 Zeus 看到人类的伟大,随即同意 Zeus 可以向人类求助。你能证明人类的智慧么?
Input
输入包含若干组测试数据,每组测试数据包含若干行。
输入的第一行是一个整数T(T < 10),表示共有T组数据。
每组数据的第一行输入两个正整数N,M(<1=N,M<=100000),接下来一行,包含N个正整数,代表 Zeus 的获得的集合,之后M行,每行一个正整数S,代表 Prometheus 询问的正整数。所有正整数均不超过2^32。
Output
对于每组数据,首先需要输出单独一行”Case #?:”,其中问号处应填入当前的数据组数,组数从1开始计算。
对于每个询问,输出一个正整数K,使得K与S异或值最大。
Sample Input
2
3 2
3 4 5
1
5
4 1
4 6 5 6
3
Sample Output
Case #1:
4
3
Case #2:
4
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+7;
int trie[maxn*32][2];
int idx[maxn*32],cnt =0;
int ans,l,r;
void Trie_insert(int x)
{
int p = 0;
for(int i=31; i>=0; i--)
{
int t = (x>>i)&1;
if(!trie[p][t])trie[p][t] = cnt++;
p = trie[p][t];
}
idx[p] = x;
}
void init()
{
memset(trie,0,sizeof trie);
cnt = 1;
ans =0 ;
l = 0;
r =0 ;
memset(idx,0x3f3f3f3f,sizeof idx);
}
int Trie_query(int x)
{
int p = 0;
int m = 0;
for(int i=31; i>=0; i--)
{
int t = (x>>i)&1;
if(trie[p][t^1])
{
p = trie[p][t^1];
m+=(1<<i);
}
else
{
p = trie[p][t];
m+=(0<<i);
}
}
return idx[p];
}
int main()
{
//freopen("in.txt","r",stdin);
int T,n,tmp,m;
scanf("%d",&T);
int id = 0;
while(T--)
{
scanf("%d%d",&n,&m);
init();
for(int i=1; i<=n; i++)
{
scanf("%d",&tmp);
Trie_insert(tmp);
}
printf("Case #%d:\n",++id);
for(int i=1;i<=m;i++)
{
scanf("%d",&tmp);
int ans = Trie_query(tmp);
printf("%d\n",ans);
}
}
return 0;
}
4。HDU - 5536
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5536
题意:有一个数组a[], 包含n个数,从n个数中找到三个数使得 (a[i]+a[j])⊕a[k]最大,i,j,k不同;
求异或的结果最大所以我们可以用01字典树,先把所有的数加入字典树中,从n个数中选出两个数a[i]和a[j],
先把他们从字典树中删除,然后找到与a[i]+a[j]异或最大的数,和结果取最大值即可;
最后不要忘记再把a[i]和a[j]添加到字典树中即可;
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3+7;
int trie[maxn*32][2],a[maxn];
int idx[maxn*32],num[maxn*32],cnt =0;
int ans,l,r;
void Trie_insert(int x,int op)
{
int p = 0;
for(int i=31; i>=0; i--)
{
int t = (x>>i)&1;
if(!trie[p][t])trie[p][t] = cnt++;
p = trie[p][t];
num[p]+=op;
}
idx[p] = x;
}
void init()
{
memset(num,0,sizeof num);
memset(trie,0,sizeof trie);
cnt = 1;
ans =0 ;
l = 0;
r =0 ;
memset(idx,0x3f3f3f3f,sizeof idx);
}
int Trie_query(int x)
{
int p = 0;
int m = 0;
for(int i=31; i>=0; i--)
{
int t = (x>>i)&1;
if(trie[p][t^1]&&num[trie[p][t^1]]>0)
{
p = trie[p][t^1];
}
else
{
p = trie[p][t];
}
}
return idx[p]^x;
}
int main()
{
//freopen("in.txt","r",stdin);
int T,n,tmp,m;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
init();
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
Trie_insert(a[i],1);
}
int yy = 0;
for(int i=1; i<=n; i++)
{
Trie_insert(a[i],-1);
for(int j=1; j<=n; j++)
{
if(i==j)continue;
Trie_insert(a[j],-1);
ans = Trie_query(a[i]+a[j]);
yy = max(yy,ans);
Trie_insert(a[j],1);
}
Trie_insert(a[i],1);
}
printf("%d\n",yy);
}
return 0;
}
5。
bzoj 4260
题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4260
让你在长为n的数组里找两个不相交的连续区间,使得两个区间分别的异或和求和之后最大
反正看到连续区间异或和最大,我只会一个套路,就是01字典树了,先正着来一遍前缀异或和,同时DP表示到i为止,前面的区间异或和最大是多少,然后倒着来一遍后缀异或和.
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e5+7;
int trie[maxn*32][2],a[maxn],dp[maxn];
int idx[maxn*32],num[maxn*32],cnt =0;
int pre[maxn],suff[maxn];
int ans,l,r;
void Trie_insert(int x)
{
int p = 0;
for(int i=31; i>=0; i--)
{
int t = (x>>i)&1;
if(!trie[p][t])trie[p][t] = cnt++;
p = trie[p][t];
}
idx[p] = x;
}
void init()
{
memset(num,0,sizeof num);
memset(trie,0,sizeof trie);
cnt = 1;
}
int Trie_query(int x)
{
int p = 0;
int m = 0;
for(int i=31; i>=0; i--)
{
int t = (x>>i)&1;
if(trie[p][t^1])
{
p = trie[p][t^1];
}
else
{
p = trie[p][t];
}
}
return idx[p]^x;
}
int main()
{
//freopen("in.txt","r",stdin);
int T,n,tmp,m;
scanf("%d",&n);
pre[0] = suff[n+1] = 0;
init();
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
pre[i] = pre[i-1]^a[i];
}
for(int i=n; i>=1; i--)
suff[i] = suff[i+1]^a[i];
memset(dp,0,sizeof dp);
Trie_insert(0);
for(int i=1;i<=n;i++)
{
dp[i] = max(dp[i-1],Trie_query(pre[i]));
Trie_insert(pre[i]);
}
init();
Trie_insert(0);
int ans =0;
for(int i=n;i>=1;i--)
{
ans = max(ans,Trie_query(suff[i])+dp[i-1]);
Trie_insert(suff[i]);
}
cout<<ans<<endl;
return 0;
}
6。POJ3764
题目传送门:http://poj.org/problem?id=3764
这题是树上的最大异或和路径,但是其实也是一样的套路,在dfs的时候,把从根到当前节点的异或和,去01字典树里查询,找到一条路径和当前路径异或和最大,这找到的绝对是两条相连的,因为你往01字典树里扔的,就是从根到当前节点的异或和
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
const int maxn = 1e5+7;
int trie[maxn*32][2],a[maxn];
int idx[maxn*32],num[maxn*32],cnt =0;
int ans,l,r;
int tot = 0;
int head[maxn];
struct node
{
int v,next,c;
}edge[maxn*2];
void addedge(int u,int v,int w)
{
edge[tot].v= v;
edge[tot].c = w;
edge[tot].next = head[u];
head[u] = tot++;
}
void Trie_insert(int x)
{
int p = 0;
for(int i=31; i>=0; i--)
{
int t = (x>>i)&1;
if(!trie[p][t])trie[p][t] = cnt++;
p = trie[p][t];
}
idx[p] = x;
}
void init()
{
memset(num,0,sizeof num);
memset(head,-1,sizeof head);
tot = 1;
memset(trie,0,sizeof trie);
cnt = 1;
ans = 0;
}
int Trie_query(int x)
{
int p = 0;
int m = 0;
for(int i=31; i>=0; i--)
{
int t = (x>>i)&1;
if(trie[p][t^1])
{
p = trie[p][t^1];
}
else
{
p = trie[p][t];
}
}
return idx[p]^x;
}
void dfs(int u,int fa,int c)
{
Trie_insert(c);
for(int i=head[u];~i;i=edge[i].next)
{
int v =edge[i].v;
if(v==fa)continue;
ans = max(ans,Trie_query(c^edge[i].c));
dfs(v,u,c^edge[i].c);
}
}
int main()
{
//freopen("in.txt","r",stdin);
int T,n,tmp,m,a,b,c;
while(cin>>n)
{
init();
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&a,&b,&c);
addedge(a,b,c);
addedge(b,a,c);
}
dfs(0,-1,0);
cout<<ans<<endl;
}
return 0;
}
7。CSU-1216: 异或最大值-trie-01字典树
http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1216
给定一些数,求这些数中两个数的异或值最大的那个值
任意两数最大异或值
这个问题可以用01-字典树很好地解决
即把所有数先按二进制从高到低位看成字符串插入trie。
枚举每个数,作为X,然后去trie里尽可能找每一位与X的二进制位相反的数,不断更新答案
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
const int maxn = 1e5+7;
int trie[maxn*32][2],a[maxn];
int idx[maxn*32],num[maxn*32],cnt =0;
int ans,l,r;
int tot = 0;
void Trie_insert(int x)
{
int p = 0;
for(int i=31; i>=0; i--)
{
int t = (x>>i)&1;
if(!trie[p][t])trie[p][t] = cnt++;
p = trie[p][t];
}
idx[p] = x;
}
void init()
{
memset(num,0,sizeof num);
tot = 1;
memset(trie,0,sizeof trie);
cnt = 1;
ans = 0;
}
int Trie_query(int x)
{
int p = 0;
int m = 0;
for(int i=31; i>=0; i--)
{
int t = (x>>i)&1;
if(trie[p][t^1])
{
p = trie[p][t^1];
}
else
{
p = trie[p][t];
}
}
return idx[p]^x;
}
int main()
{
//freopen("in.txt","r",stdin);
int T,n,tmp,m,b,c;
while(cin>>n)
{
init();
for(int i=1;i<=n;i++){scanf("%d",&a[i]);
Trie_insert(a[i]);
}
for(int i=1;i<=n;i++)
{
ans = max(ans,Trie_query(a[i]));
}
printf("%d\n",ans);
}
return 0;
}