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;
}