CF 1150 D Three Religions——序列自动机优化DP

题目:http://codeforces.com/contest/1150/problem/D

老是想着枚举当前在给定字符串的哪个位置,以此来转移。

所以想对三个串分别建 trie 树,然后求出三个trie树上各选一个点的答案。那么从“在三个trie树的根,在给定字符串的0位置”开始扩展。

当然 TLE 了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
using namespace std;
const int N=1e5+5,M=1005;
int n,m,tot=3,nw[5],fa[M],c[M][30],fl[M],q2[M];
char s[N];
struct Dt{
  int x,y,z;
  Dt(int x=0,int y=0,int z=0):x(x),y(y),z(z) {}
  bool operator< (const Dt &b)const
  {
    if(x!=b.x)return x<b.x;
    if(y!=b.y)return y<b.y; return z<b.z;
  }
  bool operator== (const Dt &b)const
  {return x==b.x&&y==b.y&&z==b.z;}
}qr[M],I;
struct Node{
  Dt a;int ps;
  Node(Dt a=I,int p=0):a(a),ps(p) {}
  bool operator< (const Node &b)const
  {return a<b.a;}
};
queue<Node> q;
map<Dt,bool> mp;
void add(int &x,int w)
{
    if(c[x][w])x=c[x][w];////if!!!!!
    else{ c[x][w]=++tot; fa[tot]=x; x=tot;}
}
void get_fl(int rt)
{
  int he=0,tl=0;
  for(int i=0;i<26;i++)
    if(c[rt][i])fl[c[rt][i]]=rt,q2[++tl]=c[rt][i];
    else c[rt][i]=rt;
  while(he<tl)
    {
      int k=q2[++he],pr=fl[k];
      for(int i=0;i<26;i++)
    if(c[k][i])fl[c[k][i]]=c[pr][i],q2[++tl]=c[k][i];
    else c[k][i]=c[pr][i];

    }
}
void cz(Dt cr,int ps)
{
  for(int x=cr.x;x;x=fl[x])
    for(int y=cr.y;y;y=fl[y])
      for(int z=cr.z;z;z=fl[z])
    if(!mp[cr]){mp[cr]=1; q.push(Node(cr,ps));}
    else return;
}
int main()
{
  scanf("%d%d",&n,&m); scanf("%s",s+1);
  nw[1]=1; nw[2]=2; nw[3]=3;
  char op,w;int d;
  for(int i=1;i<=m;i++)
    {
      cin>>op; scanf("%d",&d);
      if(op==‘+‘){cin>>w;add(nw[d],w-‘a‘);}
      else nw[d]=fa[nw[d]];
      qr[i]=Dt(nw[1],nw[2],nw[3]);
    }
  for(int i=1;i<=3;i++)get_fl(i);
  Dt cr=Dt(1,2,3); mp[cr]=1; q.push(Node(cr,0));
  while(q.size())
    {
      Node k=q.front();q.pop();
      if(k.ps==n)continue;
      k.ps++; q.push(k); int w=s[k.ps]-‘a‘;
      cr=k.a; cr.x=c[cr.x][w]; cz(cr,k.ps);
      cr=k.a; cr.y=c[cr.y][w]; cz(cr,k.ps);
      cr=k.a; cr.z=c[cr.z][w]; cz(cr,k.ps);
    }
  for(int i=1;i<=m;i++)
    puts(mp.count(qr[i])?"YES":"NO");
  return 0;
}

给定字符串的一个位置可能使得三个串都不能扩展。所以考虑不枚举字符串的每个位置来转移,而是做一个序列自动机。

令 dp[i][j][k] 表示三个地区分别匹配了前 i 、j、k 个字符的“最靠前位置”,如果值==n+1说明无解。

那么就可以使用序列自动机实现 2503 的DP了。就是 dp[i][j][k] = min( nxt[ dp[i-1][j][k] ][ c1[i] ] , nxt[ dp[i][j-1][k] ][ c2[j] ] , nxt[ dp[i][j][k-1] ][ c3[k] ] ) 。

对于一个询问,如果是 ‘ - ‘ ,DP数组不用改动。如果是 ‘ + ‘ ,固定该维, 2502 做一下DP即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int rdn()
{
  int ret=0;bool fx=1;char ch=getchar();
  while(ch>‘9‘||ch<‘0‘){if(ch==‘-‘)fx=0;ch=getchar();}
  while(ch>=‘0‘&&ch<=‘9‘)ret=ret*10+ch-‘0‘,ch=getchar();
  return fx?ret:-ret;
}
int Mn(int a,int b){return a<b?a:b;}
const int N=1e5+5,M=255,K=26;
int n,lst[K],nxt[N][K],dp[M][M][M],l0,l1,l2;
char s[N];int c0[M],c1[M],c2[M];
int main()
{
  n=rdn(); int Q=rdn();
  scanf("%s",s+1);
  for(int i=0;i<K;i++)
    lst[i]=nxt[n+1][i]=n+1;
  for(int i=n;i>=0;i--)
    {
      for(int j=0;j<K;j++) nxt[i][j]=lst[j];
      if(i)lst[s[i]-‘a‘]=i;
    }
  char op;int x,w;
  while(Q--)
    {
      cin>>op; x=rdn()-1;
      if(op==‘+‘)
    {
      cin>>op; w=op-‘a‘;
      if(x==0)
        {
          c0[++l0]=w;
          for(int i=0;i<=l1;i++)
        for(int j=0;j<=l2;j++)
          {
            dp[l0][i][j]=nxt[dp[l0-1][i][j]][w];
            if(i)dp[l0][i][j]=
               Mn(dp[l0][i][j],nxt[dp[l0][i-1][j]][c1[i]]);//i not l1
            if(j)dp[l0][i][j]=
               Mn(dp[l0][i][j],nxt[dp[l0][i][j-1]][c2[j]]);
          }
        }
      if(x==1)
        {
          c1[++l1]=w;
          for(int i=0;i<=l0;i++)
        for(int j=0;j<=l2;j++)
          {
            dp[i][l1][j]=nxt[dp[i][l1-1][j]][w];
            if(i)dp[i][l1][j]=
               Mn(dp[i][l1][j],nxt[dp[i-1][l1][j]][c0[i]]);
            if(j)dp[i][l1][j]=
               Mn(dp[i][l1][j],nxt[dp[i][l1][j-1]][c2[j]]);
          }
        }
      if(x==2)
        {
          c2[++l2]=w;
          for(int i=0;i<=l0;i++)
        for(int j=0;j<=l1;j++)
          {
            dp[i][j][l2]=nxt[dp[i][j][l2-1]][w];
            if(i)dp[i][j][l2]=
               Mn(dp[i][j][l2],nxt[dp[i-1][j][l2]][c0[i]]);
            if(j)dp[i][j][l2]=
               Mn(dp[i][j][l2],nxt[dp[i][j-1][l2]][c1[j]]);
          }
        }
    }
      else
    {
      if(x==0)l0--; if(x==1)l1--; if(x==2)l2--;
    }
      puts(dp[l0][l1][l2]<=n?"YES":"NO");
    }
  return 0;
}

原文地址:https://www.cnblogs.com/Narh/p/10822349.html

时间: 2024-11-08 01:14:07

CF 1150 D Three Religions——序列自动机优化DP的相关文章

异或序列 [set优化DP]

也许更好的阅读体验 \(\mathcal{Description}\) 有一个长度为 \(n\)的自然数序列 \(a\),要求将这个序列分成至少 \(m\) 个连续子段 每个子段的价值为该子段的所有数的按位异或 要使所有子段的价值按位与的结果最大,输出这个最大值 \(T\)组询问 \(T\leq 10,n,m\leq 1000,a_i\leq 2^{30}\) \(\mathcal{Solution}\) 实际上数据范围可开大很多 我们贪心的一位一位的确定最终答案,即看当前考虑的位能否为\(1\

序列自动机

好像很高端的样子... 其实真的很简单... 序列自动机只有当数的类型比较少的时候才适用,一般出现字符串中,所以我们不妨设我们要处理的为字符串$A$ 然后我们只需要记住第$i$个位置后字母$j$最早出现的位置即可. 就是这么简单 时间复杂度$O(26N)$ 序列自动机的功能就是能遍历所有的子序列 好像非常冷门的样子...... #include<cstdio> #include<cstdlib> #include<iostream> #include<fstrea

BZOJ 4032 HEOI2015 最短不公共子串 后缀自动机+序列自动机+BFS

题目大意:给定字符串A和B,求A最短的子串/子序列S满足S不是B的子串/子序列 这题真TM有毒*2 搞法类似这道题 然后子串是后缀自动机 子序列自然就是序列自动机了= = 每更新一个x节点时所有没有x的后继的节点都连向这个节点 每个节点的parent是这个字母上一次出现的位置 每个字母记录最后一次出现的位置 更新指针时沿着parent指针撸一遍就行了 #include <cstdio> #include <cstring> #include <iostream> #in

【bzoj3675】[Apio2014]序列分割 斜率优化dp

原文地址:http://www.cnblogs.com/GXZlegend/p/6835179.html 题目描述 小H最近迷上了一个分隔序列的游戏.在这个游戏里,小H需要将一个长度为n的非负整数序列分割成k+1个非空的子序列.为了得到k+1个子序列,小H需要重复k次以下的步骤: 1.小H首先选择一个长度超过1的序列(一开始小H只有一个长度为n的序列--也就是一开始得到的整个序列): 2.选择一个位置,并通过这个位置将这个序列分割成连续的两个非空的新序列. 每次进行上述步骤之后,小H将会得到一定

SMO(序列最小优化)——SVM

SMO(序列最小优化)引子:坐标上升法目标为: 坐标上升算法: 即每次只变化一个维度,取得该维度的最优值.例图: 参数收敛的方向都是平行于坐标轴的.  SMO算法: 由于我们要解决的问题中有一个约束是: 所以不可能只变化其中一个变量,因此需要选择两个变量来进行变化(其中一个变量可以由另外一个变量根据上式获得). α可能在最优值旁振荡,但w(α)一定会逐步趋向最优,顶多保持不变.

AC 自动机优化

AC 自动机优化 今天来小谈一下AC自动机的优化. 1:在 trie 树中被搜过的点不用再搜第二遍,标记一下,因为答案已经统计入答案中. 2:有点类似路径压缩的感觉. 1 if (int y=e[x].nxt[i]) 2 { 3 e[e[x].nxt[i]].fail=e[e[x].fail].nxt[i]; 4 q[++tail]=y; 5 } 6 else e[x].nxt[i]=e[e[x].fail].nxt[i]; 如果  nxt  数组为空,则 假设个虚拟节点,然后将它连入下一个 

Nowcoder contest 392 J (模板题)【序列自动机】

<题目链接> 题目大意:给你一个母串A,然后进行q次询问,每次询问给定一个长度不超过母串的字符串,问你这个字符串是否是母串的子串. 解题分析:序列自动机模板题.本题的思想就是考虑贪心的去匹配,我们希望当前匹配到的位置越靠前越好.所以对于母串每一位都记一下下一次出现某个字符的位置.匹配的时候从第零位(虚根)开始,如果能一直匹配下去就是$Yes$ ,否则就是 $No$,这些操作就是用序列自动机简单实现. 单次查询的复杂度是$O(|B_i|)$ 的,序列自动机预处理的复杂度是 $O(26|A|)$的

杭电多校HDU 6586 String(序列自动机 贪心)题解

题意: 给你一个串,现需要你给出一个子序列,满足26个约束条件,\(len(A_i) >= L_i\) 且 \(len(a) <= R_i\), \(A_i\)为从a到z的26个字母. 思路: 先用序列自动机(?)构造出某个位置后每个字母的个数,每个字母 的第一个位置. 然后每次贪心地加入最小的字符,加入的条件为当前字母加入后,后面的字符满足剩余的条件. 即剩余的字母在不超R_i的情况下能构成k长度的串,剩余的字母\(A_i+\)已拿取字母\(A_i >= L_i\)且满足\(L_i\)

序列自动机 浅谈

其实这个东西真的算自动机吗?好像还真的符合自动机的定义啊: 我将在下面用人话来定义序列自动机,并不像某度某科一样不说人话: 设一个字符集S,nxt[i][j]表示第i个位置往后第一个j元素出现的位置: 这个nxt数组可以O(n)的求出来,可以自行验证: for(int i=n-1;i>=0;--i){ for(int j=1;j<=26;++j) nxt[i][j]=nxt[i+1][j]; nxt[i][s[i+1]-'a'+1]=i+1; } 我们会发现一个神奇的事情:这是一个DAG! 她