2020 Multi-University Training Contest 3---- HDU--6791、Tokitsukaze, CSL and Palindrome Game(回文自动机)

题目链接

题面:
在这里插入图片描述
题意:
给定一个串 S S S ,从中选出两个回文子串。
E ( s ) E(s) E(s) 表示从一个空串 P P P 中不断随机插入新字符直到 s s s 第一次出现在 P P P 中的时 P P P 的期望长度。
多次询问要你比较选出的两个子串回文子串的期望大小。

官方题解:
在这里插入图片描述

主要是:
a i a_i ai 表示 A [ 1 , i ] A[1,i] A[1,i] 是否是 A A A 的一个 b o r d e r border border a i a_i ai为 1 表示是,0 表示不是。
那么 E ( A ) = ∑ i = 1 L a i ∗ m i E(A)=\sum_{i=1}^La_i*m^i E(A)=i=1Laimi
此结论不知道而且不易证出。

由上述结论可知,本题只需要比较 S a . . b S_{a..b} Sa..b S c . . . d S_{c...d} Sc...d b o r d e r border border 长度由大到小排序后的字典序即可。
回文串的 b o r d e r border border都是回文串。那么找到 S a . . b S_{a..b} Sa..b S c . . . d S_{c...d} Sc...d在回文自动机上面对应的节点,沿着 fail 指针跳到根,所经过每个节点都是他们的 b o e d e r boeder boeder。每个节点到根最多是 logn 个等差数列,那么总共花费 logn 的时间即可比较大小,其中我们还需要花费 logn 的时间倍增找到找到 S a . . b S_{a..b} Sa..b S c . . . d S_{c...d} Sc...d在回文自动机上面对应的节点。

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<queue>
#include<bitset>
#include<map>
#include<unordered_map>
#include<set>
#include<list>
#define ui unsigned int
#define ll long long
#define llu unsigned ll
#define ld long double
#define pr make_pair
#define pb push_back
#define lc (cnt<<1)
#define rc (cnt<<1|1)
//#define len(x)  (t[(x)].r-t[(x)].l+1)
#define tmid ((l+r)>>1)
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
using namespace std;

const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const double dnf=1e18;
const int mod=1e9+7;
const double eps=1e-5;
const double pi=acos(-1.0);
const int hp=13331;
const int maxn=1000100;
const int maxp=1100;
const int maxm=4000100;
const int up=1000;
const int N=26;

struct Palindromic_Tree
{
    int t[maxn][N];
    int fail[maxn];
    int cnt[maxn];
    int num[maxn];
    int len[maxn];
    int S[maxn];
    int di[maxn];//节点x与fail[x]长度之差
    int pre[maxn];//上一个等差序列的最后一项。
    int pos[maxn];
    int f[maxn][20];
    int ft;
    int last;
    int n;
    int p;

    int newnode(int l)
    {
        for(int i=0;i<N;++i) t[p][i]=0;
        cnt[p]=0;
        num[p]=0;
        len[p]=l;
        return p++;
    }

    void init(void)
    {
        p=0;
        newnode(0);
        newnode(-1);
        last=0;
        n=0;
        S[n]=-1;
        fail[0]=1;
        pre[0]=1;
    }

    int get_fail(int x)
    {
        while(S[n-len[x]-1]!=S[n]) x=fail[x];
        return x;
    }

    void add(int c,int i)
    {
        c-='a';
        S[++n]=c;
        int cur=get_fail(last);
        if(!t[cur][c])
        {
            int now=newnode(len[cur]+2);
            fail[now]=t[get_fail(fail[cur])][c];
            t[cur][c]=now;
            num[now]=num[fail[now]]+1;
            di[now]=len[now]-len[fail[now]];
            pre[now]=(di[now]==di[fail[now]])?pre[fail[now]]:fail[now];
        }
        last=t[cur][c];
        pos[i]=last;
        cnt[last]++;
    }

    void build_fail_tree(void)
    {
        ft=log2(n)+1;
        for(int i=2;i<p;i++)
        {
            memset(f[i],0,sizeof(f[i]));
            f[i][0]=fail[i];
            for(int j=1;j<=ft;j++)
                f[i][j]=f[f[i][j-1]][j-1];
        }
    }

    int get(int x,int le)
    {
        x=pos[x];
        for(int i=ft;i>=0;i--)
        {
            if(len[f[x][i]]>=le) x=f[x][i];
        }
        return x;
    }

    int cmp(int x,int y)
    {
        while(x>=2&&y>=2)
        {
            if(len[x]!=len[y]) return len[x]<len[y]?-1:1;
            else if(di[x]!=di[y]) return di[x]>di[y]?-1:1;
            x=pre[x],y=pre[y];
        }
        if(x<=2&&y<=2) return 0;
        else if(x<=2) return -1;
        else return 1;
    }

    void _count(void)
    {
        for(int i=p-1;i>=0;--i) cnt[fail[i]]+=cnt[i];
    }
}pam;


int n,q;
char str[maxn];

int main(void)
{
    int tt;
    scanf("%d",&tt);
    while(tt--)
    {
        scanf("%d%s",&n,str+1);
        pam.init();
        for(int i=1;i<=n;i++)
            pam.add(str[i],i);

        pam.build_fail_tree();

        int a,b,c,d;
        scanf("%d",&q);
        for(int i=1;i<=q;i++)
        {
            scanf("%d%d%d%d",&a,&b,&c,&d);
            int op=pam.cmp(pam.get(b,b-a+1),pam.get(d,d-c+1));
            if(op==0) printf("draw\n");
            else if(op==-1) printf("sjfnb\n");
            else printf("cslnb\n");
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值