zoukankan      html  css  js  c++  java
  • 【洛谷4590】[TJOI2018] 游园会(DP套DP重入门)

    点此看题面

    大致题意: 给定一个字符串,对于每一个(i),问有多少由"N","O","I"组成的长度为(m)的字符串,满足其中不存在连续的"NOI",且和给定串的最长公共子序列长度为(i)

    前言

    今天看到机房里有人在做(DP)(DP)题,先是一脸懵逼觉得这是什么鬼东西,似乎从没听说过,结果回头转念一想发现去年(ZJOI)(T1)好像就是这个东西。。。

    然后找到一道去年看到没写的(DP)(DP)题,重新学学这玩意。

    (DP)(DP)

    考虑我们在什么情况下会用到(DP)(DP)

    一般就是为满足某种状态的东西(DP)计数,而这种状态不太好维护,又需要(DP)转移。

    上面可能说得有点抽象,那么就以这题为例,题目中要求(LCS),可如果单纯地(DP)很难直接维护出(LCS)的信息。而众所周知,(LCS)这东西本身是可以(DP)求的,因此就需要(DP)(DP)

    实际上,针对这种问题,有一个比较容易理解的做法,就是对于一层(DP)改为建立一个自动机,而另一层(DP)改为在自动机上(DP)(相信大家都会(AC)自动机上(DP)吧),例如这题我们就可以建立一个(LCS)自动机。

    (LCS)自动机

    (f_{now,i})表示当前串与给定串前(i)位的(LCS),则(f_{lst,i})就表示前一个串与给定串前(i)位的(LCS),并令(x)为当前加入的字符。

    考虑(LCS)的基本转移方程:(其中(s)为给定串)

    [f_{now,i}=max{f_{lst,i},f_{now,i-1},f_{lst,i-1}+[x=s_i]} ]

    不难发现,每一种(f)数组就对应着(LCS)自动机上的一个状态。

    你可以像麻将那题一样直接用(map)来维护,不过实际上这题有一种复杂度更优秀的做法,就是状压。

    考虑相邻的(f_{i-1})(f_i)之间最多相差(1),于是我们只要状压差值,就可以高效地压缩一个状态了。

    建立这个自动机时,我们可以令(S_{x,w})表示状态(x)在加入字符(w)之后会转移到哪一状态。

    自动机上(DP)

    仿照一般自动机上(DP)的套路,设(f_{i,j,0/1/2})表示当前为第(i)个字符、在自动机上的(j)号点、连续出现了"NOI"中(0/1/2)个字符的方案数。

    转移只要枚举当前加入的字符(w),就可以转移到(f_{i+1,S_{j,w},0/1/2})(其中第三维只要分保持原先的连续和重新开始两种情况讨论即可)。

    这一过程实际上还是比较简单的,具体实现详见代码。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define M 15
    #define X 1000000007
    #define Inc(x,y) ((x+=(y))>=X&&(x-=X))
    using namespace std;
    int n,l,s[M+5];char st[M+5];
    class LCS_Automation
    {
    	private:
    		int lim,a[M+5],f[M+5],S[1<<M][3],cnt[1<<M],dp[2][1<<M][3];
    		I void Extend(CI x)//求出x的后继状态
    		{
    			RI i;for(i=0;i^l;++i) a[i+1]=a[i]+((x>>i)&1);//解码
    			RI j,t;for(i=0;i^3;++i)//枚举加入字符
    			{
    				for(j=1;j<=l;++j) f[j]=max(max(a[j],f[j-1]),a[j-1]+(s[j]==i));//DP
    				for(t=j=0;j^l;++j) t|=(f[j+1]-f[j])<<j;S[x][i]=t;//状压差值
    			}
    		}
    	public:
    		I void Build() {lim=1<<l;for(RI i=0;i^lim;++i) Extend(i);}//建立LCS自动机
    		int ans[M+5];I void DP()//LCS自动机上DP
    		{
    			#define Cls(t) memset(dp[t],0,sizeof(dp[t]))
    			RI i,j,k,w,t;for(Cls(0),dp[0][0][0]=1,t=i=0;i^n;++i,t^=1) for(Cls(t^1),j=0;j^lim;++j)//注意滚存
    				for(k=0;k^3;++k) for(w=0;w^3;++w) (k^2||w^2)&&Inc(dp[t^1][S[j][w]][k^w?!w:k+1],dp[t][j][k]);//枚举加入字符转移
    			for(i=0;i^lim;++i) cnt[i]=cnt[i>>1]+(i&1),//显然cnt[i]就是状态i的LCS
    				Inc(ans[cnt[i]],dp[t][i][0]),Inc(ans[cnt[i]],dp[t][i][1]),Inc(ans[cnt[i]],dp[t][i][2]);//统计答案
    		}
    }A;
    int main()
    {
    	RI i;for(scanf("%d%d%s",&n,&l,st+1),i=1;i<=l;++i) s[i]=st[i]^'N'?(st[i]^'O'?2:1):0;//把字符改为数码
    	for(A.Build(),A.DP(),i=0;i<=l;++i) printf("%d
    ",A.ans[i]);return 0;//建自动机,然后自动机上DP
    }
    
  • 相关阅读:
    多线程伪共享FalseSharing
    C语言restrict限定符
    Linux线程基础函数
    Linux信号函数
    C函数前向声明省略参数
    12.2 关闭DLM 自动收集统计信息 (SCM0)ORA-00600之[ksliwat: bad wait time]
    pdb的数量限制
    关闭或开启memory_target
    OSWATCH安装
    参数SID写错,ERROR OGG-00664 ORA-01034: ORACLE not available ORA-27101: shared memory realm does not exist
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4590.html
Copyright © 2011-2022 走看看