"蔚来杯"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

留坑待填...

posted @ 2022-08-16 09:59  逆天峰  阅读(198)  评论(0编辑  收藏  举报
作者:逆天峰
出处:https://www.cnblogs.com/gcfer//