NEFU大一暑假集训-字典树

题集链接

OP

感谢学长的讲解与付出;
感谢ph和zsl两位大佬的指导与讨论;

目前来看,字典树主要用于线性复杂判断给定字符串与字典有没有前缀关系,和给定目标下异或值的求取;

A L语言

题目大意

给定字典,问题给字符串是否为字典中的若干单词拼接起来的;

思路

参考

个人选择了一个比较暴力的做法(只看懂了这个);
即对每一个作为单词结尾的点的后面都进行单词判断,以此更新最大长度;
详见上链吧;

代码

代码来自上链

#include <bits/stdc++.h>
using namespace std;
map<string, bool> m1;
map<string, int> m2;
char s[2000005];
int cnt = 0;
int trie[1005][28];
bool f[2000010];
bool flag[10005];
inline int read()
{
    int ans = 0, fl = 1;
    char i = getchar();
    while (i < '0' || i > '9')
    {
        if (i == '-')
        {
            fl = -1;
        }
        i = getchar();
    }
    while (i >= '0' && i <= '9')
    {
        ans = ans * 10 + i - '0';
        i = getchar();
    }
    return ans * fl;
}
inline void insert()
{
    scanf("%s", s + 1);
    int len = strlen(s + 1);
    int pos = 0;
    for (int i = 1; i <= len; i++)
    {
        int c = s[i] - 'a';
        if (!trie[pos][c])
        {
            trie[pos][c] = ++cnt;
        }
        pos = trie[pos][c];
    }
    flag[pos] = 1;
}
inline int query() //查询
{
    scanf("%s", s + 1);
    if (m1[s + 1])
    {
        return m2[s + 1];
    }
    else
    {
        int len = strlen(s + 1);
        int pos = 0;
        int ans;
        memset(f, false, sizeof(f));
        f[0] = true;
        for (int i = 0; i <= len; i++)
        {
            if (!f[i])
                continue;
            else
                ans = i;
            for (int j = i + 1, p = 0; j <= len; j++)
            {
                int c = s[j] - 'a';
                p = trie[p][c];
                if (!p)
                    break;
                if (flag[p])
                    f[j] = true;
            }
        }
        m1[s + 1] = true;
        m2[s + 1] = ans;
        return ans;
    }
}
int main()
{
    int n, m;
    n = read();
    m = read();
    while (n--)
        insert();
    while (m--)
        cout << query() << endl;
    return 0;
}

B Secret Message 秘密信息

题目大意

用以测试的串是多少个给定串的前缀;

思路

字典树板,用给定串构造好字典树,用测试串去匹配即可;

注意一些特殊情况,如测试串是给定串的前缀等,做好标记;

代码
#include <stdio.h>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string.h>
using namespace std;
#pragma GCC optimize(2)
typedef long long ll;
typedef int itn;
typedef unsigned long long ull;

inline ll read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return (ll)x*f;
}
int b[100005],g;
int ttre[500005][2],meet[500005],ed[500005],c;
int main()
{
    int n,m;
    cin>>n>>m;
    c=1;
    while(n--)
    {
        
        int k;
        scanf("%d",&k);
        int p=0;
        for(int i=1;i<=k;i++)
        {
            scanf("%d",&g);
            if(!ttre[p][g])ttre[p][g]=c++;//printf("*");
            p=ttre[p][g];
            meet[p]++;
        }
        meet[p]--;
        ed[p]++;
    }
    while(m--)
    {
        int ans=0;
        c=1;
        int k;
        scanf("%d",&k);
        int p=0;
        for(int i=1;i<=k;i++)
        {
            scanf("%d",&b[i]);
        }
        for(int i=1;i<=k;i++)
        {
            p=ttre[p][b[i]];
            if(!p)
            {
                //ans+=meet[p];
                break;
            }
            else
            {
                ans+=ed[p];
                
            }
        }
        ans+=meet[p];
        printf("%d\n",ans);
    }
    return 0;
}

C The XOR-longest Path

题目大意

给定树的结构和每条边的边权,求任意两点间路径上的最小边权异或值;

思路

树的性质有,任意两点间均有且只有一条路径;

异或的性质有, C = A x o r B , C x o r B = A C=AxorB,CxorB=A C=AxorB,CxorB=A

所以,我们可以通过dfs处理出所有点与一号节点(根)之间路径的边权异或,这样无论所求两点是否为同一枝杈上,都可以 O ( 1 ) O(1) O(1) 复杂度内求出两点间的路径边权异或值;

接下来把这些处理出的异或值存入字典树,依次在字典树中匹配与其异或值最大的值,并更新答案;此部分复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

代码
#include <stdio.h>
#include <iostream>
#include <stack>
#include <vector>
#include <algorithm>
#include <string.h>
using namespace std;
#pragma GCC optimize(2)
typedef long long ll;
typedef int itn;
typedef unsigned long long ull;

inline ll read()
{
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
        x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    return (ll)x * f;
}
//stack<int> stk;
int ttre[3200006][2], ed[3200006], val[100005], rch[100005]; //val[300005];
vector<pair<int, int> > rod[100005];

void dfs(int x)
{
    for (int i = 0; i < rod[x].size(); i++)
    {
        if (!rch[rod[x][i].first])
        {
            rch[rod[x][i].first] = 1;
            val[rod[x][i].first] = (val[x] ^ rod[x][i].second);
            dfs(rod[x][i].first);
        }
    }
}
int main()
{
    int n, g, a, b, c;
    while (cin >> n)
    {
        
        for (int j = 1; j <= n - 1; j++)
        {
            scanf("%d%d%d", &a, &b, &c);
            rod[a].push_back(make_pair(b, c));
            rod[b].push_back(make_pair(a, c));
        } //printf("*");
        rch[1] = 1;
        dfs(1);
        c = 1;
        //printf("*");
        for (int j = 1; j <= n; j++)
        {
            int p = 0;
            for (int i = 30; i >= 0; i--)
            {
                g = (val[j] >> i) & 1;
                if (!ttre[p][g])
                    ttre[p][g] = c++;
                p = ttre[p][g];
            }
            //ed[p]++;
        }
        int ans = 0;
        for (int i = 1; i <= n; i++)
        {
            int tmp = 0, p = 0;
            for (int j = 30; j >= 0; j--)
            {
                if (ttre[p][!((val[i] >> j) & 1)])
                {
                    tmp += 1 << j;
                    p = ttre[p][!((val[i] >> j) & 1)];
                }
                else
                    p = ttre[p][((val[i] >> j) & 1)];
            }
            ans = max(ans, tmp);
        }
        cout << ans << endl;
        for (int i = 1; i <= n; i++)
        {
            val[i] = 0;
            rch[i] = 0;
            rod[i].clear();
        }
        for (int i = 0; i < c; i++)
        {
            ttre[i][0] = 0;
            ttre[i][1] = 0;
        }
    }
    return 0;
}

D The XOR Largest Pair

题目大意

给定一些数,求任意两数间的异或最大值;

思路

字典树板,将这些数存入字典树,依次在字典树中匹配与其异或值最大的值,并更新答案;

代码
#include <stdio.h>
#include <iostream>
#include <stack>
#include <algorithm>
#include <string.h>
using namespace std;
#pragma GCC optimize(2)
typedef long long ll;
typedef int itn;
typedef unsigned long long ull;

inline ll read()
{
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
        x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    return (ll)x * f;
}
stack<int>stk;
int ttre[3100006][2],ed[3100006],k[100005];//val[300005];
int c;
int ans=0;

int main()
{
    int n,g;
    cin >> n;
    c=1;
    for(int j=1;j<=n;j++)
    {
        scanf("%d", &k[j]);
        int p = 0;
        for (int i = 30; i>=0; i--)
        {
            g = (k[j]>>i)&1;
            //printf("%d*",g);
            if (!ttre[p][g])
                ttre[p][g] = c++;
            p = ttre[p][g];
            //val[p]=g;
        }
        ed[p]++;
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        int tmp=0,p=0;
        for(int j=30;j>=0;j--)
        {
            if(ttre[p][!((k[i]>>j)&1)])
            {
                tmp+=1<<j;
                p=ttre[p][!((k[i]>>j)&1)];
            }
            else  p=ttre[p][((k[i]>>j)&1)];
        }
        ans=max(ans,tmp);
    }
    cout<<ans;
    return 0;
}

E Phone List

题目大意

给定 n 个长度不超过 10 且各异的数字串,问其中是否存在两个数字串 S,T,使得 S 是 T 的前缀。

思路

字典树板,接收到的都存入字典树后,检测字典树中是否有前缀关系即可;

注意不能用 lld 接收,存在前导零;

代码
#include <stdio.h>
#include <iostream>
#include <stack>
#include <algorithm>
#include <string.h>
using namespace std;
#pragma GCC optimize(2)
typedef long long ll;
typedef int itn;
typedef unsigned long long ull;

inline ll read()
{
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
        x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    return (ll)x * f;
}
int g;
int ttre[100005][10], meet[100005], ed[100005], c;
char k[12];
int main()
{
    int n;
    itn t;
    cin >> t;
    while (t--)
    {
        int f = 0;
        cin >> n;
        c = 1;
        while (n--)
        {
            scanf("%s",&k);
            int p = 0;
            for (int i = 0; k[i]; i++)
            {
                g = k[i] - '0';
                //printf("%d*",g);
                if (!ttre[p][g])
                    ttre[p][g] = c++;
                p = ttre[p][g];
                meet[p]++;
            }
            //meet[p]--;
            ed[p]++;
        }
        for (int i = 0; i < c; i++)
        {
            if (ed[i] && meet[i] >= 2)
                f = 1;
            ed[i]=0;
            meet[i]=0;
            memset(ttre[i],0,sizeof ttre[i]);
        }
        if (f)
            printf("NO\n");
        else
            printf("YES\n");
    }
    return 0;
}

F Xor sum

题目大意

给定n个数和一个数k,求出最短的连续的一段数,使得它们的异或和大于等于k,如果没有则输出-1。

思路

参考

对于给定的每一个数,在存入字典树之前先判断并更新答案,这样搜索范围内的只有它前面的数,此时即为固定右端点找左端点;

每个数在树中存储的结尾要记录这个数最后一次出现的下标,这样就可以使区间尽可能小;

注意无论这位的异或值是1还是0,只要合法,就应计算,之后取最大值;

代码

代码来自上链

#include <stdio.h>
#include <iostream>
#include <stack>
#include <vector>
#include <algorithm>
#include <string.h>
using namespace std;
#pragma GCC optimize(2)
typedef long long ll;
typedef int itn;
typedef unsigned long long ull;

inline ll read()
{
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
        x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    return (ll)x * f;
}
const int N = 1e5 + 10;
int trie[N * 31][2], mx[N * 31], fa[N * 31], tot = 0;

void init()
{
    trie[0][0] = trie[0][1] = 0;
    for (int i = 0; i <= tot; i++)
    {
        mx[i] = fa[i] = 0;
    }
    tot = 0;
}

void insert(int x, int pos)
{
    int temp[31];
    for (int i = 30; i >= 1; i--)
    {
        temp[i] = x & 1;
        x >>= 1;
    }
    int now = 0;
    for (int i = 1; i <= 30; i++)
    {
        int p = now;
        mx[now] = pos;
        if (trie[now][temp[i]])
            now = trie[now][temp[i]];
        else
        {
            trie[now][temp[i]] = ++tot;
            trie[tot][0] = trie[tot][1] = 0;
            now = tot;
        }
        fa[now] = p;
    }
    mx[now] = pos;
}

int query(int r, int k, int now, int sum, int bit)
{
    if (sum >= k)
        return mx[now];
    if (sum + (1 << (bit + 1)) <= k)
        return 0;
    int ans = 0;
    if (trie[now][0])
    {
        int temp = (r & (1 << bit));
        ans = max(ans, query(r, k, trie[now][0], sum + temp, bit - 1));
    }
    if (trie[now][1])
    {
        int temp = (r & (1 << bit) ^ (1 << bit));
        ans = max(ans, query(r, k, trie[now][1], sum + temp, bit - 1));
    }
    return ans;
}

int main()
{
    int T = 1;
    T = read();
    while (T--)
    {
        init();
        int n, k;
        n = read();
        k = read();
        int ans = n + 1, l = -1, r = -1;
        int sum = 0;
        for (int i = 1; i <= n; i++)
        {
            int a = read();
            sum ^= a;
            if (sum >= k)
            {
                if (ans > i)
                {
                    ans = i;
                    l = 1;
                    r = i;
                }
            }

            int d = query(sum, k, 0, 0, 29);
            if (d > 0 && ans > i - d)
            {
                ans = i - d;
                l = d + 1;
                r = i;
            }

            insert(sum, i);
        }
        if (ans == n + 1)
            printf("-1\n");
        else
            printf("%d %d\n", l, r);
    }
}

ED

\

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值