首页 > 其他分享 >DP 套 DP 与 游园会

DP 套 DP 与 游园会

时间:2024-11-27 19:44:37浏览次数:4  
标签:兑奖 int 游园会 长度 自动机 节点 DP

DP 套 DP

听名字猜不到它是个什么东西。

接下来用一道例题 P459 TJOI2018 游园会 来解释 DP 套 DP。


游园会

参考资料

题目描述

小豆参加了 NOI 的游园会,会场上每完成一个项目就会获得一个奖章,奖章只会是 \(\texttt{N}\)、\(\texttt{O}\)、\(\texttt{I}\) 的字样。在会场上他收集到了 \(K\) 个奖章组成的串。兑奖规则是奖章串和兑奖串的最长公共子序列长度为小豆最后奖励的等级。现在已知兑奖串长度为 \(N\),并且在兑奖串上不会出现连续三个奖章为 \(\texttt{NOI}\),即奖章中不会出现子串 \(\texttt{NOI}\)。现在小豆想知道各个奖励等级会对应多少个不同的合法兑奖串。

输入格式

第一行两个数,\(N, K\) 分别代表兑奖串的长度,小豆收集的奖章串的长度(\(N\leq1000,K\leq15\))。

第二行一共 \(K\) 个字符,表示小豆得到的奖章串。

输出格式

一共 \(K+1\) 行,第行表示小豆最后奖励等级为 \(i-1\) 的不同的合法兑奖串的个数,可能这个数会很大,结果对 \(10^9+7\) 取模。

数据范围

  • \(N\leq1000,K\leq15\)。

题目大意

对于每个 \(i\) 求满足以下条件的字符串数量:

  • 长度为 \(n\),字符集为 \(N,O,I\)。
  • 不包含子串 NOI
  • 与给定的长为 \(k\) 的字符串的最长公共子序列的长度为 \(i\)。

分析 & 解法

每个位置都可以选其中一种字符,求最后的方案数。

这样的问题考虑 DP 求解,好处是我们不用考虑可能的重复计数的情况,因为 DP 的转移保证了包含所有情况。

考虑设 \(dp_{i,...}\),表示长度为 \(i\) 的字符串,后面有一些奇奇怪怪的状态限制。那么接下来就枚举下一个字符是 \(N,O,I\) 中的哪一个。

我们看不包含子串 NOI 这个限制,我们可以记录 \(0/1/2\) 表示当前字符串的后缀匹配 NOI 的长度。

那么问题就是 与给定的长为 k 的字符串的最长公共子序列的长度为 i,看起来很棘手。

如果是一般的字符串限制,我们可以考虑记录当前字符串匹配到对应的自动机上的节点编号,将编号作为 DP 状态。这样我们在转移 DP时,选取一个新字符,就可以知道在自动机上转移后新的节点编号。

或者反过来在自动机上做 DP。

然而我们并没有 最长公共子序列自动机

除了自动机,那有什么东西加一个字符可以转移呢?就是 DP。

我们考虑大家是怎么做最长公共子序列的长度的。

设 \(f_{i,j}\) 表示 \(A\) 的前 \(i\) 个字符和 \(B\) 的前 \(j\) 个字符的答案,则显然有:

\[f_{i,j}=max\{f_{i-1,j},f_{i,j-1},f_{i-1,j-1}+[A_i=B_j]\}。 \]

那我们就类似于记录自动机上的节点编号。

当字符串长度为 \(i\) 时,我们考虑记录 \(f_i\) 这个一维数组长什么样,作为状态。

首先在这里,我们的 \(B\) 串是固定已知的,我们每次转移时就要知道:\(f_{i-1}\) 这个一维数组长什么样,\(A_i\) 是什么。

\(f_{i-1}\) 已经设在状态里了,\(A_i\) 正是我们枚举的当前这一位置选什么。


至此 DP 套 DP 的雏形就出来了。

我们设 DP 状态 \(dp_{i,f_i,0/1/2}\),这里 \(f_i\) 实际是一个编号,它代表一个 DP 数组。

也就是说,我们把一个 DP 设为另一个 DP 的状态,这就是 DP 套 DP 名字的由来。

然而,到这里远没结束,我们发现我们这个 \(f_i\) 的种类实在太多了,考虑去掉无用状态,这就是 DP 套 DP 都要做的。


很多 \(f_i\) 不合法。

  • 根据 \(f\) 的定义,对于任意 \(i\),\(f_i\) 这个数组是单调的。

  • 进一步地,\(f_i\) 相邻两项最多相差 \(1\)。

于是我们把原来的数组的差分表达为一个 \(01\) 串。

然后将 \(01\) 串视为一个数的二进制表示,于是我们的节点数最多为 \(O(2^k)\)。

用上面所说的状态跑一遍 DP 即可。

状态数为 \(O(3\times n\times2^k)\),需要滚动数组。

另外,我们可以像自动机一样,对 \(f_i\) 预处理每个节点选一个新的节点后的后继节点。

当然,你也可以选择每次当场转移后继节点。

我们的时间还要乘上枚举的新字符数,即 \(3\)。

总时间复杂度 \(O(3\times 3\times n\times 2^k)\),空间复杂度 \(O(3\times 2^k)\)。


代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n,K;
char B[20];
int a[20];
void add(int &x,int y){
	x=(x+y)%mod;
}
int nx[1<<16][3];
int f[2][1<<16][3];
int ans[20];
#define popcnt(x) __builtin_popcount(x)
int main(){
	cin>>n>>K;
	scanf("%s",B+1);
	for(int i=1;i<=K;i++){
		if(B[i]=='N')a[i]=0;
		else if(B[i]=='O')a[i]=1;
		else a[i]=2;
	}
	for(int i=0;i<1<<K;i++){
		for(int k=0;k<3;k++){
			int tmp[20];
			tmp[0]=0;
			for(int j=1,x=i;j<=K;j++,x>>=1){
				tmp[j]=x&1;
				tmp[j]+=tmp[j-1];
			}
			int nw[20];
			nw[0]=0;
			for(int j=1;j<=K;j++){
				nw[j]=max(max(nw[j-1],tmp[j]),tmp[j-1]+(a[j]==k));
				int x=nw[j]-nw[j-1];
				if(x)nx[i][k]+=1<<j-1;
			}
		}
	}
	f[0][0][0]=1;
	for(int i=1;i<=n;i++){
		memset(f[i&1],0,sizeof f[i&1]);
		for(int k=0;k<3;k++){
			for(int l=0;l<3;l++){
				int nk;
				if(k==l)nk=k+1;
				else{
					nk=0;
					if(l==0)nk=1;
				}
				if(nk<=2){
					for(int j=0;j<1<<K;j++){
						add(f[i&1][nx[j][l]][nk],f[(i&1)^1][j][k]);
					}
				}
			}
		}
	}
	for(int i=0;i<1<<K;i++){
		add(ans[popcnt(i)],f[n&1][i][0]);
		add(ans[popcnt(i)],f[n&1][i][1]);
		add(ans[popcnt(i)],f[n&1][i][2]);
	}
	for(int i=0;i<=K;i++)cout<<ans[i]<<"\n";
}

评测记录

大功告成!

于是我们就可以去刷题了!

例题

jzoj 8114

P8352 SDOI/SXOI2022 小 N 的独立集

P5279 [ZJOI2019] 麻将

P8497 [NOI2022] 移除石子

标签:兑奖,int,游园会,长度,自动机,节点,DP
From: https://www.cnblogs.com/dccy/p/18572968

相关文章

  • NOIP 冲刺之——dp
    \(\texttt{0x00}\)前言本篇文章主要记录笔者NOIP冲刺阶段复习的各种dp题型及tricksanstips,同时也用于及时复习与巩固。那么,开始吧。\(\texttt{0x01}\)线性dp线性dp对我来说是一类很捉摸不定的题型:她太综合了,可以和任何知识点合起来考,这里就先抛开“数据结构优化”......
  • 动态规划 区间dp 基础题
    题目19182石子合并(基础版)时间限制:1000MS代码长度限制:10KB提交次数:0通过次数:0题型:编程题语言:不限定Description设有N(N≤300)堆石子排成一排,其编号为1,2,3,⋯,N。每堆石子有一定的质量mi(mi≤1000)。现在要将这N堆石子合并成为一堆。每次只能合并相邻的两堆,......
  • jmeter测试udp接口详解
    jmeter测试udp广播(jmeter发送udp)jmeter测试udp广播(jmeter接收udp)先下载安装第三方插件下载链接:https://jmeter-plugins.org/install/Install/ 将下载的插件放在lib/ext目录里面 然后重启jmeter,如下图操作:          此时可以看到lib/ext目录里面多了......
  • 【DP优化技巧】2. 矩阵加快
    例题来看一道例题。P5024[NOIP2018提高组]保卫王国对于这道题,首先如果没有国王的询问,可以设定状态:\(f_{i,0/1}\)代表以\(i\)为根的子树里面,自己选/不选的最小花费。易得状态转移方程:\[f_{u,0}=\sum_{v\inson_u}f_{v,1}\\f_{u,1}=p_u+\sum_{v\inson_u}\min(f_{v,0},......
  • 数据结构优化DP
    数据结构优化DP参考题单CleaningShiftsS区间覆盖问题区间加区间最值线段树维护cin>>n>>m>>e;m++,e++;for(inti=1;i<=n;i++) c[i].in();T.build(1,1,e);sort(c+1,c+1+n,[](nodea,nodeb){ if(a.l==b.l)returna.r<b.r; returna......
  • 嵌入式开发之UDP网络编程
    1、TCP编程的函数API1.1、网络发送数据:send()/write()#include<sys/types.h>#include<sys/socket.h>ssize_tsend(intsockfd,constvoid*buf,size_tlen,intflags);#include<unistd.h>ssize_twrite(intfd,constvoid*buf,size_tcount);send()比write多......
  • AT_tdpc_knapsack ナップザック
    定义:\(A_c\)表示颜色为\(c\)的物品集合,\(g\otimesS\)表示背包\(g\)中插入集合\(S\)中的所有物品后的新背包。刚开始想的是对于每种颜色,求出各自背包,然后进行\(lim_c\)次\(O({lim_w}^2)\)的合并。显然T了,考虑漏了什么:本来操作\(g\otimesS\)是\(O(|g|\cdot......
  • [ABC234G] Divide a Sequence (DP+单调栈)题解
    分析题意十分简单,不难推出式子:$f_i=\sum_{j=1}^{i-1}f_j\times(\max_{k=j+1}^ia_k-\min_{k=j+1}^ia_k)$但我们考虑这个\(O(n^2)\)的东西显然是冲不过去的,所以必须优化转移。式子后面两块都是极值,这启发我们用单调栈解决。由于\(\max_{k=j+1}^i\)与\(\min_{k=......
  • 子集和dp
    子集和dp用处统计n维偏序,但是每一维的大小只能是2。计算子集权值之和。实际上以上两种问题是等价的。例如目前有一个集合:101(其中1表示有某个物品,0表示没有)。那该集合包涵的子集有4个:101,100,001,000。现在要把这4个集合的权值加起来。按照第二种理解(用处),我们可以一位一位地......
  • .NET Core 线程池(ThreadPool)底层原理浅谈
    简介上文提到,创建线程在操作系统层面有4大无法避免的开销。因此复用线程明显是一个更优的策略,切降低了使用线程的门槛,提高程序员的下限。.NETCore线程池日新月异,不同版本实现都有差别,在.NET6之前,ThreadPool底层由C++承载。在之后由C#承载。本文以.NET8.0.8为蓝本,如有出入,请......