"蔚来杯"2022牛客暑期多校训练营9部分题题解
G Magic Spells
对回文自动机都快忘完了...比赛的时候在磨蹭了一个多小时后,终于下定决心去搞这个题。
回文自动机的一些基本属性记清楚即可:
回文自动机可以把一个串中的所有回文子串都给搞出来。且时间空间都是O(n)的。并且构成一个tire树,每个节点都有一个失配指针(fail指针).它的定义为:当前节点所构成的回文串与之匹配的最长后缀的节点。当然所匹配的节点一定也是回文串。和后缀自动机有点类似,我们沿着某个节点一直往上跳fail,可以找到以当前节点为后缀的所有回文串。
当然回文自动机和后缀自动机也是有很大区别的,它是一个严格的tire树,(0,1除外,他俩当作一个根)。fail指针也构成一个棵树,并且fail指针和后缀自动机的fail指针一样,都代表现在的节点可以,之前节点的也可以的信息。所以这道题我们完全可以对于第一个串建一个回文自动机,然后让其它串在当前串上面跑,之后再将节点信息传向父节点即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
int k,n,ans;
int len[N],fail[N],tire[N*26][26],cnt,last;
char c[N];
bool End[N][7];
inline void PAM()
{
fail[0]=1;len[1]=-1;cnt=1;
c[0]='#';
for(int i=1;i<=n;++i)
{
while(c[i-len[last]-1]!=c[i]) last=fail[last];//尝试为当前节点增长.
int ch=c[i]-'a';
if(!tire[last][ch])
{
len[++cnt]=len[last]+2;
int j=fail[last];
while(c[i-len[j]-1]!=c[i]) j=fail[j];
fail[cnt]=tire[j][ch];
tire[last][ch]=cnt;
}
last=tire[last][ch];//更新last
}
}
int main()
{
// freopen("1.in","r",stdin);
scanf("%d%s",&k,c+1);
n=strlen(c+1);
PAM();
for(int i=2;i<=k;++i)
{
scanf("%s",c+1);
c[0]='#';n=strlen(c+1);
int p=0;
for(int j=1;j<=n;++j)
{
int ch=c[j]-'a';
while(c[j-len[p]-1]!=c[j]||(p!=1&&!tire[p][ch])) p=fail[p];//尝试为当前节点增长.
if(!tire[p][ch]) continue;
p=tire[p][ch];
End[p][i]=1;
}
}
int ans=0;
for(int i=cnt;i>=2;--i)
{
for(int j=2;j<=k;++j) End[fail[i]][j]|=End[i][j];
bool flag=true;
for(int j=2;j<=k;++j) if(!End[i][j]) flag=false;
if(flag) ans++;
}
printf("%d\n",ans);
return 0;
}
I The Great Wall II
大眼一看,直接确定是单调栈优化DP的问题,于是直接按照上次线段树+单调栈去写了,写完才发现T了,确实8000的数据量感觉就是为了卡n^2log才出这么大的,老老实实想正解吧。
正解肯定还是用单点栈去做的,考虑能不能优化掉线段树的log(我承认是我单调栈没学好),考虑能不能在做单调栈的同时,去维护最小值,什么意思呢?就是说我们另外再开一个数组\(sta_min[N]\)。它表示栈中第i-1个元素到第i个元素所代表区间内的所有DP候选集合的最小值的前缀和。仔细品品,发现这个东西我们完全可以再做单调栈的时候顺便更新。那我们就完全没必要用线段树了。
(仔细想想,这个题为什么能直接用单调栈去维护,貌似因为转移条件简单,只与最大值有关,我们完全可以用前缀和的思想去优化。)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=8010;
int f[N][N],n,a[N];//f[j][i]表示前i个数划分成j个区间的最小值.
int top,Stack[N],sta_min[N];
int main()
{
// freopen("1.in","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=0;i<=n;++i) f[0][i]=1e9;
f[0][0]=0;
for(int j=1;j<=n;++j)//划分成j个区间.
{
top=0;
for(int i=1;i<=n;++i)
{
int res=1e9;
while(top&&a[i]>=a[Stack[top]])
{
res=min(res,sta_min[top]-a[Stack[top]]+a[i]);
top--;
}
if(top) res=min(res,sta_min[top]);
Stack[++top]=i;
res=min(res,f[j-1][i-1]+a[i]);
sta_min[top]=res;
f[j][i]=res;
}
printf("%d\n",f[j][n]);
f[j][0]=1e9;
}
return 0;
}
E Longest Increasing Subsequence
留坑待填...