2019徐州icpc网络赛补题

G :给你一个字符串求所有的回文子串中不同种类字母的数量(每个回文子串单独计算)

做法首先建立回文树,并求出与每个节点i回文串出现的次数,然后分别在偶节点0和奇节点0跑dfs,

dfs的点来自next树,边表示字符串c,节点表示回文子串,则每次dfs到一个节点,答案更新为该点的回文串出现次数*已有的字符数种类数。字符种类数用一个26大小的vis数组存每次找到下一个点则vis[边]++, 回溯则vis[边]-- 如果是新边则dfs下一个节点的时候为种类数+1,否则就为种类数。该题会爆int 所以要用longlong 存。

#include<bits/stdc++.h>
#define ll long long
#define maxn 300010   //节点个数
using namespace std;
char a[maxn];
int vis[50];
struct pam
{
    int len[maxn];  //len[i] 节点i的回文串长度
    int nxt[maxn][26]; //nxt[i]['c'] 节点i的回文串在两边添加字符c后变成的回文串编号
    int fail[maxn]; //fail[i] 指向i的最长回文后缀所在的节点 且不为i
    int cnt[maxn]; //cnt[i] 节点i表示的回文串在S中出现的次数
    int s[maxn]; //s[i] 第i次添加的字符
    int num[maxn];
    int last; //指向以字符串中上一个位置结尾的回文串的节点
    int cur; //指向由next构成的树中当前回文串的父亲节点(即当前回文串是cur左右两边各拓展一个字符得来)
    int p; // 添加的节点个数
    int n; // 添加的字符串个数
    int newnode(int l)  //新建节点
    {
        for(int i=0;i<=25;i++)nxt[p][i]=0; // 消除子节点
        cnt[p]=num[p]=0;  //节点p为新回文串所以出现次数为0
        len[p]=l;
        return p++;
    }
    inline void init()
    {
        p=n=last=0;
        newnode(0);  //偶节点
        newnode(-1); //奇节点
        s[0]=-1;
        fail[0]=1;
    }
    int get_fail(int x) //找到可以插入的节点
    {
        while(s[n-1-len[x]]!=s[n])x=fail[x];
        return x;
    }
    inline void add(int c)
    {
        c-='a';
        s[++n]=c;
        int cur=get_fail(last); // 找到可以插入的节点并当做父节点
        if(!nxt[cur][c])
        {
            int now=newnode(len[cur]+2);
            fail[now]=nxt[get_fail(fail[cur])][c]; //从父节点的回文后缀开始找,找到一个s[l-1]=s[n]的点则出边的点为最长回文后缀
            nxt[cur][c]=now;
            num[now]=num[fail[now]]+1;
        }
        last=nxt[cur][c];  //成为新的上一个位置
        cnt[last]++;
    }
    void Count() //统计本质相同的回文串的出现次数。与位置无关
    {
        for(int i=p-1;i>=0;i--)
        {
            cnt[fail[i]]+=cnt[i]; //每个节点会比父节点先算完,于是父节点能加到所有的子节点
        }
    }
}pam;
ll ans=0;
void dfs(int x,int step)
{
    //printf("%d %d\n",x,step);
    if(x>1)
    {
        ans+=1ll*pam.cnt[x]*step;
    }
    for(int i=0;i<='z'-'a';i++)
    {
        int v=pam.nxt[x][i];
        if(!v)continue;
        else
        {
            if(vis[i]==0)
            {
                vis[i]++;
                dfs(v,step+1);
                vis[i]--;
            }
            else
            {
                vis[i]++;
                dfs(v,step);
                vis[i]--;
            }
        }
    }
}
int main()
{
    scanf("%s",a);
    pam.init();
    int len=strlen(a);
    for(int i=0;i<len;i++)
    {
        pam.add(a[i]);
    }
    pam.Count();
    memset(vis,0,sizeof(vis));
    dfs(0,0);
    memset(vis,0,sizeof(vis));
    dfs(1,0);
    printf("%lld\n",ans);
    return 0;
}

M 给你两个长度分别为n和m的字符串s和t,在s串中找一个最长的子序列使这个子序列严格大于字符串t

做法首先序列自动机从后往前预处理出s串第i位之后(包括第i位)第一次比字符j大的字符的位置。

然后让s串和t串匹配

1.s串比t串的开头大的第一个位置

2.s串的字符和t串的字符相等,则找到s串比t串的下一个字符第一次大的位置。更新答案,然后t串继续下一个字符

如果又和s串相等,则继续找比t串下一个字符第一次大的位置。直到s串或者t串被遍历完。

3.如果是t串先被更新完,则更新答案为已经匹配的长度加上s串未匹配完的长度。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1000115;
int n,m;
int d[maxn][27];
char a[maxn];
char b[maxn];
int main()
{
    scanf("%d %d",&n,&m);
    scanf("%s",a+1);
    scanf("%s",b+1);
    for(int i=n;i>=1;i--)
    {
        int x=a[i]-'a';
        for(int j=0;j<='z'-'a';j++)
        {
            if(j<x)d[i][j]=i;
            else d[i][j]=d[i+1][j];
        }
    }
    int ans=-1;
    int p=1;
    if(d[1][b[1]-'a'])ans=max(ans,n-d[1][b[1]-'a']+1);
    for(int i=1;i<=n;i++)
    {
        if(a[i]==b[p])
        {
            p++;
            if(p>m)
            {
                if(n-i)
                {
                    ans=max(ans,n-i+p-1);
                    break;
                }
            }
            if(i+1<=n&&d[i+1][b[p]-'a'])
            {
                ans=max(ans,n-d[i+1][b[p]-'a']+p);
            }
        }
    }
    printf("%d",ans);
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值