LOJ 一本通提高篇2.3Trie字典树 例题+练习(坑)

复习时食用,会比较简略。

原理就不讲了,还不会字典树的先下车吧。


目录

#10049. 「一本通 2.3 例 1」Phone List

#10050. 「一本通 2.3 例 2」The XOR Largest Pair

#10051. 「一本通 2.3 例 3」Nikitosh 和异或

#10052. 「一本通 2.3 练习 1」Immediate Decodability

#10053. 「一本通 2.3 练习 2」L 语言

#10054. 「一本通 2.3 练习 3」Secret Message 秘密信息

#2012. 「SCOI2016」背单词

#10056. 「一本通 2.3 练习 5」The XOR-longest Path


#10049. 「一本通 2.3 例 1」Phone List

 

#10050. 「一本通 2.3 例 2」The XOR Largest Pair

题目

题目大意

在给定的N个整数中选出两个进行异或运算。

得到的结果最大是多少?

对于100%的数据,1<=n<=10^5,0<=ai<2^31。

题目分析

百度科普:异或即两个输入相同时为0,不同则为1。

首首先化成二进制建树。

为了让异或值max当然是要01反着跑,即一边跑向1另一边跑向0

化为二进制后第i位不同则:ans+=2^(i-1)。

每插入一个数就跑一遍,要么你建完树再跑也行。

建树从高位到低位。废话肯定先捡大便宜再捡小便宜。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int n,len=0;
long long a[100010],ans=0,biao=1;
struct node{int son[10];}tr[3100010];

void bt(long long aa)
{
	int now=0,x,d=0,k[40];
	long long kkk=biao,aans=0;
	for(int i=1;i<=32;i++)
	{
		if(aa>=kkk) x=1,aa-=kkk;
		else x=0;
		if(tr[now].son[x]==0)
		{
			len++,tr[now].son[x]=len,now=len;
			tr[now].son[0]=tr[now].son[1]=0;
		}
		else now=tr[now].son[x];
		d++; k[d]=x;//k存这个数化为二进制每一位上的数 
		kkk/=2;
	}
	now=0; kkk=biao;
	for(int i=1;i<=32;i++)
	{
		//是1往0跑,是0往1跑 
		if(tr[now].son[1-k[i]]!=0) 
		{
			aans+=kkk;
			now=tr[now].son[1-k[i]];
		}
		else now=tr[now].son[k[i]];//条件不允许你乱跑 
		kkk/=2;
	}
	ans=max(ans,aans);
}

int main()
{
	scanf("%d",&n);
	tr[0].son[0]=tr[0].son[1]=0;
	for(int i=1;i<=31;i++) biao*=2;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		bt(a[i]);
	}
	printf("%lld",ans);
	return 0;
}

 

#10051. 「一本通 2.3 例 3」Nikitosh 和异或

题目

题目大意

给定一个含N个元素的数组A,下标从1开始。

找出式子的最大值:(A[l_1]⨁A[l_1+1]⨁…⨁A[r_1])+(A[l_2]⨁A[l_2+1]⨁…⨁A[r_2])。

其中1≤l_1≤r_1<l_2≤r_2≤N,x⨁y表示x和y的按位异或。

对于100%的数据,2<=n<=4*10^5,0<=ai<=10^9。

题目分析

首先记录异或前缀和s[i]=a[1]⊕a[2]⊕a[3]...⊕a[i]。

设l[i]为以i结尾的区间中,异或值的最大值。

因为异或有x⊕x=0的性质,所以区间[l,r]的异或值

=a[l]⊕a[l+1]⊕...⊕a[r]

=(a[1]⊕a[2]⊕a[3]...⊕a[l−1])⊕(a[1]⊕a[2]⊕a[3]...⊕a[r])

=s[l−1]⊕s[r]

所以求l[i],转化为找j<i,使得s[j]⊕s[i]最大。

以上引自:https://www.cnblogs.com/qwerta/p/9822368.html 代码有点奇怪??? %%%

l_1和r_1正着找,得出前半段得max值。

l_2和r_2反正再找一遍,得出后半段得max值。道理等同于以上。即

首先记录异或前缀和s[i]=a[n]⊕a[n-1]⊕a[n-2]...⊕a[1]。

设l[i]为以i开头的区间中,异或值的最大值。

因为异或有x⊕x=0的性质,所以区间[l,r]的异或值

=a[l]⊕a[l+1]⊕...⊕a[r]

=(a[l]⊕a[l+1]⊕a[l+2]...⊕a[n])⊕(a[r+1]⊕a[r+2]⊕a[r+3]...⊕a[n])

=s[l]⊕s[r+1]

应该没有毛病吧...

 注意不管正着找还是反着找都要先放一个数——0进去。为什么?

正着放时则表示从第1位到第i位,反着放时则表示从第i位到第n位。

这个应该不难懂?

从上文可以知道正着找:区间[l,r]的异或值=s[l−1]⊕s[r]

那么当l==1时,即为s[0]⊕s[r]。s[0]就是0啦。

我觉得解释得海星???我的语文水平也就这样了。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int n,len=0,ans=0,biao=1;
struct node{int son[5];}tr[12400010];
int a[400010],big[400010],l[400010],r[400010];

void bt(int aa,int q)
{
	int now=0,x,d=0,k[40];
	int kkk=biao,aans=0;
	for(int i=1;i<=31;i++)
	{
		if(aa>=kkk) x=1,aa-=kkk;
		else x=0;
		if(tr[now].son[x]==0)
		{
			len++,tr[now].son[x]=len,now=len;
			tr[now].son[0]=tr[now].son[1]=0;
		}
		else now=tr[now].son[x];
		d++; k[d]=x;
		kkk/=2;
	}
	now=0; kkk=biao;
	for(int i=1;i<=31;i++)
	{
		if(tr[now].son[1-k[i]]!=0)
		{
			aans+=kkk;
			now=tr[now].son[1-k[i]];
		}
		else now=tr[now].son[k[i]];
		kkk/=2;
	}
	big[q]=aans;
}

int main()
{
	scanf("%d",&n); l[0]=0; r[n+1]=0;
	for(int i=1;i<=30;i++) biao*=2;
	int s=0; bt(0,0);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		s^=a[i]; bt(s,i);
		l[i]=max(l[i-1],big[i]);//前半段:以i结尾 
	}
	for(int i=0;i<=len;i++)
	{
		memset(tr[i].son,0,sizeof(tr[i].son));
	}
	len=0; s=0; bt(0,0);
	for(int i=n;i>=1;i--)
	{
		s^=a[i]; bt(s,i);
		r[i]=max(r[i+1],big[i]);//后半段:以i开头 
	}
	for(int i=1;i<=n;i++)
	{
		ans=max(ans,l[i]+r[i+1]);
	}
	printf("%d",ans);
	return 0;
}

 

#10052. 「一本通 2.3 练习 1」Immediate Decodability

题目大意

给出若干数字串,判断是否有一个数字串是另一个串的前缀。

数字串只包含0,1,记每个数字串长度为l,则1<=l<=10。每组数据至少有2个数字串,至多有8个数字串。

题目分析

#10049. 「一本通 2.3 例 1」Phone List一模一样...不过换了个皮囊。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int len,d=0;
char aa[20];
bool kkk=false,a;
struct node{int end,s[3];}tr[1010];

void bt(char ss[],int s)
{
	int now=0; bool q=false;
	for(int i=1;i<=s;i++)
	{
		if(tr[now].s[ss[i]-'0'])
		{
			now=tr[now].s[ss[i]-'0'];
			if(tr[now].end) a=true;
		}
		else
		{
			len++; tr[now].s[ss[i]-'0']=len;
			now=len; tr[now].end=0; q=true;
		}
	}
	tr[now].end++;
	if(!q) a=true;
}

int main()
{
	//freopen("a.out","w",stdout);
	while(1)
	{
		a=false; len=0; d++;
		memset(tr[0].s,0,sizeof(tr[0].s));
		while(1)
		{
			if(scanf("%s",aa+1)==EOF) return 0;
			if((strlen(aa+1)==1)&&aa[1]=='9') break;
			bt(aa,strlen(aa+1));
		}
		if(a) printf("Set %d is not immediately decodable\n",d);
		else printf("Set %d is immediately decodable\n",d);
		for(int i=1;i<=len;i++)
		{
			for(int j=0;j<=1;j++) tr[i].s[j]=0;
			tr[i].end=0;
		}
	}
	return 0;
}

 

#10053. 「一本通 2.3 练习 2」L 语言

题目

题目大意

我们称一段文章T在某个字典D下是可以被理解的,是指如果文章T可以被分成若干部分,且每一个部分都是字典D中的单词。

给定一个字典D,你需要判断若干段文章在字典D下是否能够被理解。

并给出其在字典D下能够被理解的最长前缀的位置。

题目分析

一眼AC自动机?听我的不要慌。不用AC自动机我我我能做!

好吧,字典树+暴力(最喜欢暴力了...滑稽)

代码好懂?不管不管反正我觉得好懂。

一些解释放代码里了。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

char a[1050010];
bool v[1050010];
int n,m,len=0,ans;
struct node{int son[28];bool end;}tr[210];

void bt(char s[],int l)//建树 
{
	int now=0;
	for(int i=1;i<=l;i++)
	{
		int x=s[i]-'a';
		if(tr[now].son[x]) now=tr[now].son[x];
		else
		{
			len++; tr[now].son[x]=len;
			now=len; tr[now].end=false;
		}
	}
	tr[now].end=true;
}

void find(int c,int l)
{
	if(v[c]) return ;
	//如果当前位置被访问过了,那么不会有比之前访问过的更优的情况了 
	//please仔细想想为什么! 
	int now=0; v[c]=true;
	ans=max(ans,c-1);//必须要是完整的单词
	for(int i=c;i<=l;i++)
	{
		int x=a[i]-'a';
		if(tr[now].son[x])
		{
			now=tr[now].son[x];
			if(tr[now].end) find(i+1,l);
			//可以作为一个新的开头来 
		}
		else break;
	}
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",a+1);
		bt(a,strlen(a+1));
	}
	for(int i=1;i<=m;i++)
	{
		memset(v,false,sizeof(v));
		ans=0; scanf("%s",a+1);
		find(1,strlen(a+1));
		printf("%d\n",ans);
	}
	return 0;
}

 

#10054. 「一本通 2.3 练习 3」Secret Message 秘密信息

 

#2012. 「SCOI2016」背单词

我太菜了还不会,留坑。猛然惊觉我好多坑没填。

 

#10056. 「一本通 2.3 练习 5」The XOR-longest Path

题目

题目大意

给定一棵n个点的带权树,求树上最长的异或和路径。

对于100%的数据,1<=n<=10^5,1<=u,v,<=n,0<=w<2^31。

题目分析

甚妙。我努力解释一下。

根据每个结点到根结点的异或值建一棵字典树。

在字典树上努力向两边跑。道理同理于#10050. 「一本通 2.3 例 2」The XOR Largest Pair

从而得出最大答案。

同理#10050. 「一本通 2.3 例 2」The XOR Largest Pair,你可以每插入一个数就跑一遍,建完树再跑也行。

那么问题来了,为什么可以这样做???

无非两种情况

1、两个数在根节点的同侧。

 举个例子:1为根节点,2为1的儿子,3为2的儿子(一条链)。那么我们事先处理好了1到2的异或值1到3的异或值。(敲黑板重点来了!!!)我们可以直接异或1到2的异或值1到3的异或值然后得到2到3的异或值。因为异或有x⊕x=0的性质。所以任意两个在根节点同侧的点可以得到异或值。

2、两个数在根节点的异侧。

再举个例子:1为根节点,2为1的第一个儿子,3为1的第二个儿子(他好像并没有保证是二叉树)。那么2到3的异或值显然能表示为1到2的异或值 异或 1到3的异或值。

所以一切好像都没什么毛病。

注意!每个数到根节点的异或值也可以作为答案。

那么,以上。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

long long biao=1,ans=0;
int n,len=0,tot=0,a[100010];
struct nod1{int y,gg;long long c;}b[200010];
struct nod2{int son[20];}tr[3100010];

void ins(int x,int y,long long c)
{
	len++; b[len].y=y; b[len].c=c;
	b[len].gg=a[x]; a[x]=len;
}

void bt(long long x)
{
	int now=0,ee[40]; long long kk=biao;
	for(int i=32;i>=1;i--)
	{
		int xx;
		if(x>=kk) xx=1,x-=kk;
		else xx=0;
		if(tr[now].son[xx]) now=tr[now].son[xx];
		else tot++,tr[now].son[xx]=tot,now=tot;
		kk/=2; ee[i]=xx;
	}
	now=0,kk=biao; long long ss=0;
	for(int i=32;i>=1;i--)
	{
		if(tr[now].son[1-ee[i]])
		{
			now=tr[now].son[1-ee[i]];
			ss+=kk;
		}
		else now=tr[now].son[ee[i]];
		kk/=2;
	}
	ans=max(ans,ss);
}

void dfs(int x,long long s,int fa)
{
	for(int i=a[x];i;i=b[i].gg)
	{
		int y=b[i].y;
		if(y==fa) continue;
		long long aaaaa=s^b[i].c;//每个数到根节点的异或值
		bt(aaaaa); dfs(y,aaaaa,x);
		ans=max(ans,aaaaa);
	}
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=31;i++) biao*=2;
	for(int i=1;i<n;i++)
	{
		int x,y; long long z;
		scanf("%d%d%lld",&x,&y,&z);
		ins(x,y,z); ins(y,x,z);
	}
	dfs(1,0,0);
	printf("%lld",ans);
	return 0;
}

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值