机房测试 字符串 【Hash+KMP】【分块+可持久化Trie树+贪心】【AC自动机+主席树】

第一题

无尽的矩阵(matrix.c/cpp/pas)

题目描述

从前有一个的小矩阵,矩阵的每个元素是一个字母(区分大小写),突然有一天它发生了变异,覆盖了整个二维空间,即不停自我复制产生相同的矩阵然后无隙放置。现在二维空间已经被它占领了,但你只被告知了大小为R*C空间的内容(可能包含不完整的原矩阵),为了将它恢复原状,你需要找到满足条件的面积最小的原矩阵。
奇怪的是,同时有 T 个二维空间发生了变异,你需要尽快解决这些变异。

输入格式

第一行为一个整数T,表示二维空间数目。
接下来T组数据。每组数据第一行包含两个数 R,C,表示你被告知的空间大小;接下来 R 行,每行包含 C 个字母,表示你被告知的空间内容。

输出格式

对于每一组数据输出一行,每行只包含一个数,表示最小的原矩阵面积。

样例输入

2
2 5
ABABA
ABABA
2 8
ABCDEFAB
AAAABAAA

样例输出

2
12

数据范围与约定

对于前20%的数据R<=20,C<=20;
对于前40%的数据R<=400,C<=100;
对于100%的数据R<=5000 ,C<=100,T<=50。

思路

做法1:hash求出每一行可能的循环节长度,取公共的最小循环节长度即可,列同理。将两次求得的最小循环节长度相乘即为答案。较慢,可能会超时。
做法 2:将每一行hash为一个数,对得到的新数组直接跑KMP求最小循环节长度,列同理。将两次求得的最小循环节长度相乘即为答案。这就是std做法。

代码

#include<iostream>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=10000+5;
char A[N][105];
unsigned long long base=100007,r[N],c[N],zj[N] ;
int R,C,len,fail[N];
char get_c()
{
    char c;
    while((c=(char)getchar())!=EOF) if (!isspace(c)) break;
    return c;
}
int get_it()
{
    memset(fail,0,sizeof(fail));
    for (int i=2;i<=len;++i)
    {
        int t=fail[i-1];
        while(t&&zj[t+1]!=zj[i]) t=fail[t];
        if(zj[t+1]==zj[i]) fail[i]=t+1;
    }
    return len-fail[len];
}
int main()
{
    freopen("matrix.in","r",stdin) ;
    freopen("matrix.out","w",stdout) ;
    int T; 
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&R,&C);
        memset(r,0,sizeof(r)); 
        memset(c,0,sizeof(c));
        for(int i=1;i<=R;i++) 
        for(int j=1;j<=C;j++) 
        {
            A[i][j]=get_c();
            r[i]=r[i]*base+A[i][j];
        }
        for(int j=1;j<=C;j++) 
        for(int i=1;i<=R;i++) 
        c[j]=c[j]*base+A[i][j];
        for(int i=1;i<=R;++i) 
        zj[i]=r[i];
        len=R;
        int ans=get_it();
        for(int i=1;i<=C;++i) 
        zj[i]=c[i];
        len=C;
        ans*=get_it();
        printf("%d\n",ans);
    }
    return 0 ;
}

第二题

异或(xor.c/cpp/pas)

题目描述

给出 n 个数,Q次询问,每次问[l,r]中最大连续异或和。
为了体现在线操作,对于每次询问(x,y):
l=min( ((x+lastans) mod n)+1 , ((y+lastans) mod n)+1 )
r=max( ((x+lastans) mod n)+1 , ((y+lastans) mod n)+1 )

输入格式

第一行为两个整数n,m,分别表示数的个数和询问次数。
接下来一行 n个数,再接下来 m行,每行两个数 x,y,表示给出询问(x,y),通过上述操作得到l和r,查询[l,r]中最大连续异或和。

输出格式

输出m行,每行一个整数表示该次询问的答案。

样例输入

3 3
1 4 3
0 1
0 1
4 3

样例输出

5
7
7

数据范围与约定

对于30%的数据,n<=500,Q<=500。
对于100%的数据,n<=12000 , Q<=6000 , 给出的数均在signed longint 范围内。

思路

将 n 个数分成sqrt(n)个块。考虑用 w[i][j] 表示从第 i 个块开头元素到第 j 个元素这个区间中,最大连续异或和。建可持久化Trie树并且预处理出w数组。预处理复杂度为 O(n * sqrt(n) * 位数)。

查询[l,r]时,令 p 为 l 以右第一个块开头元素,那么首先可以直接得到 p 到 r 区间的答案。再考虑上 [l,p-1] 区间中的元素,逐个在可持久化Trie上贪心即可。查询总复杂度为O(Q * sqrt(n) * 位数)。

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
typedef long long LL;
const int maxn=12005;
const int maxbit=31;
int N,M,A[maxn],tr[maxn];
int max(int x,int y)
{
    if (x>=y) return x;
    else return y;
}
struct PerTrie
{
    int next[10000005][2],num[10000005];
    int id;
    void init() {id=next[0][0]=next[0][1]=num[0]=0;}
    int f(int x,int i){return (x>>i)&1;}
    void Insert(int& rt,int pre,int x,int pos) //插入
    {
        rt=++id;
        next[rt][0]=next[pre][0];
        next[rt][1]=next[pre][1];
        num[rt]=num[pre]+1;
        if (pos==-1) return ;
        int d=f(x,pos);
        Insert(next[rt][d],next[pre][d],x,pos-1);
    }
    int MaxXor(int l,int r,int x) //查询最大异或值,因为A[i]保存
    {    //的是前缀异或值,所以得到的结果就是某一段区间的异或值
        int ret=0;
        for(int i=maxbit;i>=0;i--)
        {
            int d=f(x,i);
            int a=next[l][d^1],b=next[r][d^1];
            if(num[b]-num[a]>0) ret|=(1<<i),l=a,r=b;
            else l=next[l][d],r=next[r][d];
        }
        return ret;
    }
}PT;
int block,num,bel[maxn],dp[120][maxn];
 //dp保存第几块到第几个数的区间最大异或值
void init()
{
    tr[0]=0;
    PT.init();
    for(int i=1;i<=N;i++) PT.Insert(tr[i],tr[i-1],A[i],maxbit); //插入
    block=(int)sqrt(N+0.5);
    num=N/block;
    if(N%block) num++; //1
    memset(dp,0,sizeof(dp));
    bel[0]=0;
    for(int i=1;i<=N;i++) bel[i]=(i-1)/block+1; //记录下属于哪个块
    for(int i=1;i<=num;i++)
    {
        int st=(i-1)*block+1;
        for(int j=st;j<=N;j++)
        {
            dp[i][j]=max(dp[i][j-1],A[j]^A[st-1]);//可能是[st,j]这段
            dp[i][j]=max(dp[i][j],PT.MaxXor(tr[st-1],tr[j],A[j])); 
            //再找最大的
        }
    }
}
int GetAns(int l,int r)
{
    l--;
    int s=bel[l],ret=0;
    if (bel[r]>s) ret=dp[s+1][r]; //查询从后面一个块开始的
    for (int i=l;i<=min(r,s*block);i++)
    ret=max(ret,PT.MaxXor(tr[l-1],tr[r],A[i]));
    return ret;
}
int main()
{
    freopen("xor.in","r",stdin);
    freopen("xor.out","w",stdout);
    scanf("%d%d",&N,&M);
    A[0]=0;
    int x;
    for(int i=1;i<=N;i++)
    {
        scanf("%d",&x);
        A[i]=A[i-1]^x;
    }
    init();
    int last=0,l,r;
    while(M--)
    {
        scanf("%d%d",&l,&r);
        l=(l+(LL)last)%N+1;
        r=(r+(LL)last)%N+1;
        if(l>r) swap(l,r);
        //printf("%d %d\n",l,r);
        last=GetAns(l,r);
        printf("%d\n",last);
    }
    return 0;
}

第三题

魔法串(magic.c/cpp/pas)

题目描述

给你一棵n+1个结点的有根树,结点从0到n标号,其中0为根结点。
这是一棵魔法树。这棵树的每条边有一个魔力值,同一个结点连向不同子结点的边的魔力值不同。一个结点所代表的魔法串是从根一直走到这个结点,经过的魔力值依次排列形成的有序序列,另外,一个串是魔法串当且仅当它被一个结点所代表。
现在,为了使用强大的魔法,你需要对每个魔法串,找到最长的是它后缀的魔法串。为了方便输出,你只需要输出代表这个魔法串的结点的标号即可。若没有这个魔法串,请输出0。

输入格式

第一行一个整数n,代表除根以外的结点个数。
第二行 n个整数,第i个整数P_i代表标号为i的结点的父亲标号。
第三行 n个整数,第i个整数C_i代表标号为i的结点连向父亲的边的魔力值。

输出格式

输出一行n个整数,第i个整数表示第i个结点代表的魔法串的答案。

样例输入

7
0 0 1 1 2 4 5
1 2 3 2 1 1 3

样例输出

0 0 0 2 1 5 3

数据范围与约定

对于30%的数据,保证1<=n<=2000。
对于100%的数据,保证1<=n<=200000,0<=P_i

思路

考虑补全AC自动机(Trie图),考虑一个结点u所连出的转移边与fail[u]所连出的转移边的关系,只有u直接连出的边会影响这些转移边,而边数是n-1条。于是我们考虑将fail[u]的转移边全部复制给u,再在此基础上对u的转移边进行修改。这个如何实现?用可持久化线段树维护每个结点的转移边即可。

代码

#include<bits/stdc++.h>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
using namespace std;
const int N=200005;
const int M=6000005;
typedef long long LL;
struct edge 
{ 
    int p, v, w; 
}e[N];
struct node 
{ 
    int l, r, v; 
}t[M];
int f[N],q[N],ql,qr,rt[N],n,p[N],c[N],hed[N],e_cnt,tot;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void build(int x,int y,int w) 
{
    e[++e_cnt].p=y;
    e[e_cnt].v=hed[x];
    e[e_cnt].w=w; 
    hed[x]=e_cnt; 
}
void modify(int &x,int l,int r,int p,int v)
{
    int y=x;
    x=++tot;
    t[x]=t[y];
    if (l==r) {t[x].v=v;return ;}
    int mid=(l+r)/2;
    if (p<=mid) return modify(t[x].l,l,mid,p,v);
    else return modify(t[x].r,mid+1,r,p,v);
}
int query(int x,int l,int r,int p)
{
    if (l==r) return t[x].v;
    int mid=l+r>>1;
    if (p<=mid) return query(t[x].l,l,mid,p);
    else return query(t[x].r,mid+1,r,p);
}
int query(int x,int l,int r,int p)
{
    if (l==r) return t[x].v;
    int mid=(l+r)/2;
    if (p<=mid) return query(t[x].l,l,mid,p);
    else return query(t[x].r,mid+1,r,p);
}
int main()
{
//  freopen("magic.in","r",stdin);
//  freopen("magic.out","w",stdout);
    n=read();
    for (int i=1;i<=n;i++)
    p[i]=read();
    for (int i=1;i<=n;i++)
    {
        c[i]=read();
        build(p[i],i,c[i]);
    }
    for (int i=1;i<=n;i++)
    {
        c[i]=read();
        build(p[i],i,c[i]);
    }
    for (q[qr++]=0;ql^qr;ql++)
    {
        int x=q[ql];
        rt[x]=rt[f[x]];
        for (int i=hed[x];i;i=e[i].v)
        {
            f[q[qr++]=e[i].p]=query(rt[f[x]],1,n,e[i].w);
            modify(rt[x],1,n,e[i].w,e[i].p);
        }
    }
    for (int i=1;i<=n;i++)
    {
        printf("%d",f[i]);
        if (n==i) printf("\n");
        else printf(" ");
    }
    return 0;
}
    原文作者:Trie树
    原文地址: https://blog.csdn.net/Hawo11/article/details/76164453
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞