【XSY3896】相似(dp套dp,状压)

9 篇文章 0 订阅

题面

相似

题解

可以发现, S S S T T T 相似,等价于它们的最长公共子序列长度至少为 n − k n - k nk

首先考虑如何求出两个字符串的 LCS \text{LCS} LCS(最长公共子序列)。考虑 dp:

f i , j f_{i,j} fi,j 表示 S [ 1 ∼ i ] S[1\sim i] S[1i] T [ 1 ∼ j ] T[1\sim j] T[1j] LCS \text{LCS} LCS 长度,转移是显然的:
f i , j = { f i − 1 , j − 1 + 1 if ⁡ S [ i ] = T [ j ] max ⁡ ( f i − 1 , j , f i , j − 1 ) if ⁡ S [ i ] ≠ T [ j ] \begin{aligned} f_{i,j}= \begin{cases} f_{i-1,j-1}+1& \operatorname{if }S[i]=T[j]\\ \max(f_{i-1,j},f_{i,j-1})& \operatorname{if }S[i]\neq T[j] \end{cases} \end{aligned} fi,j={fi1,j1+1max(fi1,j,fi,j1)ifS[i]=T[j]ifS[i]=T[j]
然后考虑如何拓展到 T T T 任意的情况。自然的想法是 dp 套 dp:

d p [ j ] [ f 1 , j , f 2 , j , ⋯   , f n , j ] dp[j][f_{1,j},f_{2,j},\cdots,f_{n,j}] dp[j][f1,j,f2,j,,fn,j] 表示只考虑 T T T 的前 j j j 位,那么这 j j j 位有多少种选取方式,使此时的 f ∗ , j f_{*,j} f,j 数组如 dp 下标所示。

这个状态设计显然是可以优化的,不难证得 f i + 1 , j − f i , j ∈ { 0 , 1 } f_{i+1,j}-f_{i,j}\in\{0,1\} fi+1,jfi,j{0,1},又由于 f 0 , j = 0 f_{0,j}=0 f0,j=0,所以我们把 d p dp dp 数组的第二维变成差分数组 c i = f i + 1 − f i , j c_i=f_{i+1}-f_{i,j} ci=fi+1fi,j 的 01 串状压即可。

考虑这个 dp 的时间复杂度,状态数是 O ( n 2 n ) O(n2^n) O(n2n) 的。直接暴力 DP 的时间复杂度就约为 O ( 2 n poly ⁡ ( n ) ) O(2^n\operatorname{poly}(n)) O(2npoly(n))。考虑如何利用 k k k 很小这个性质进行优化。

首先考虑优化状态数。

考虑某个满足 ∣ i − j ∣ > k |i-j|>k ij>k f i , j f_{i,j} fi,j(显然有 f i , j ≤ min ⁡ ( i , j ) f_{i,j}\leq \min(i,j) fi,jmin(i,j)),再考虑这个 f i , j f_{i,j} fi,j 转移到 f n , n f_{n,n} fn,n 时最大会变成多少:观察最上面给出的 f i , j f_{i,j} fi,j 递推式,显然按第一种情况转移值才会变大,而第一种情况最多转移 min ⁡ ( n − i , n − j ) \min(n-i,n-j) min(ni,nj) 次,所以从 f i , j f_{i,j} fi,j 转移到 f n , n f_{n,n} fn,n 时的值不大于:
f i , j + min ⁡ ( n − i , n − j ) ≤ min ⁡ ( i , j ) + min ⁡ ( n − i , n − j ) = n − ( max ⁡ ( i , j ) − min ⁡ ( i , j ) ) < n − k f_{i,j}+\min(n-i,n-j)\leq \min(i,j)+\min(n-i,n-j)=n-(\max(i,j)-\min(i,j))<n-k fi,j+min(ni,nj)min(i,j)+min(ni,nj)=n(max(i,j)min(i,j))<nk
所以这个 f i , j f_{i,j} fi,j 不可能使 f n , n ≥ n − k f_{n,n}\geq n-k fn,nnk

于是,与其记录整个 f f f 数组,我们只需要记录 f j − k , j , f j − k + 1 , j , ⋯   , f j + k , j f_{j−k,j} , f_{j−k+1,j},\cdots, f_{j+k,j} fjk,j,fjk+1,j,,fj+k,j,这可以通过一个长度为 2 k 2k 2k 的差分数组以及一个 f j , j f_{j,j} fj,j 得到。(这个差分数组也可以用 01 串状压表示)

又由于 f j , j f_{j,j} fj,j 的值必须介于 [ j − k , j ] [j − k, j] [jk,j] 之间才能使 f n , n ≥ n − k f_{n,n}\geq n-k fn,nnk,所以我们将 d p dp dp 数组的第二维换成一个数 x ∈ [ 0 , k ] x\in [0,k] x[0,k] 表示 f j , j = j − k + x f_{j,j}=j-k+x fj,j=jk+x,第三维换成差分数组的 01 串状压。此时 d p dp dp 的总状态数只有 O ( n k 2 2 k ) O(nk2^{2k}) O(nk22k)

再考虑优化转移。

转移的过程是这样的:对于 d p [ j ] [ x ] [ s t a ] dp[j][x][sta] dp[j][x][sta],枚举一个字符 c c c T [ j + 1 ] T[j+1] T[j+1],向 d p [ j + 1 ] [ x ′ ] [ s t a ′ ] dp[j+1][x'][sta'] dp[j+1][x][sta] 转移。(其中 x ′ x' x s t a ′ sta' sta 需要我们计算出来)

为了方便转移,我们可以先预处理一个 pair 数组 t u r n [ j ] [ x ] [ s t a ] [ m a t c h ] turn[j][x][sta][match] turn[j][x][sta][match],表示当 d p [ j ] [ x ] [ s t a ] dp[j][x][sta] dp[j][x][sta] d p [ j + 1 ] [ x ′ ] [ s t a ′ ] dp[j+1][x'][sta'] dp[j+1][x][sta] 转移,枚举的字符 c c c S [ j − k + 1 ] , S [ j − k + 2 ] , ⋯   , S [ j + k + 1 ] S[j-k+1],S[j-k+2],\cdots,S[j+k+1] S[jk+1],S[jk+2],,S[j+k+1] 的匹配状态为 m a t c h match match 时, x ′ x' x s t a ′ sta' sta 分别是多少。

也就是说,我们知道了 f j − k ∼ j + k , j f_{j-k\sim j+k,j} fjkj+k,j m a t c h match match,要求此时的 f j − k + 1 ∼ j + k + 1 , j + 1 f_{j-k+1\sim j+k+1,j+1} fjk+1j+k+1,j+1

按照最上面的递推式正常转移即可。

然后你会发现一个很神奇的东西:要求出 x x x s t a sta sta 的变化量根本不需要 j j j,只需要 x x x s t a sta sta

所以 t u r n turn turn 的第一维 j j j 可以直接去掉。

所以才叫预处理 t u r n \sout{turn} turn

注意 m a t c h match match 也是可以预处理的,然后此时预处理的时间是 O ( k 2 2 2 k 2 2 k + 1 + 26 n k ) = O ( k 2 2 4 k + 1 + 26 n k ) O(k^22^{2k}2^{2k+1}+26nk)=O(k^22^{4k+1}+26nk) O(k222k22k+1+26nk)=O(k224k+1+26nk),可以接受。

转移的时间优化到了 O ( 26 n k 2 2 k ) O(26nk2^{2k}) O(26nk22k)

其实已经能跑过了,但有点卡。

所以可以进一步的优化:

你发现对于枚举的 c c c,只要 c c c 不在 S [ j − k + 1 ] , S [ j − k + 2 ] , ⋯   , S [ j + k + 1 ] S[j-k+1],S[j-k+2],\cdots,S[j+k+1] S[jk+1],S[jk+2],,S[j+k+1] 内出现过, m a t c h match match 就等于 0 0 0,也就是说它们都相等,此时得到的 x ′ x' x s t a ′ sta' sta 都是相同的。

所以说,假设 S [ j − k + 1 ] , S [ j − k + 2 ] , ⋯   , S [ j + k + 1 ] S[j-k+1],S[j-k+2],\cdots,S[j+k+1] S[jk+1],S[jk+2],,S[j+k+1] 中有 n u m num num 种字符,那么我们一次算出 m a t c h = 0 match=0 match=0 时的 x ′ x' x s t a ′ sta' sta,然后转移 d p [ j + 1 ] [ x ′ ] [ s t a ′ ] ← d p [ j + 1 ] [ x ′ ] [ s t a ′ ] + ( 26 − n u m ) d p [ j ] [ x ] [ s t a ] dp[j+1][x'][sta']\gets dp[j+1][x'][sta']+(26-num)dp[j][x][sta] dp[j+1][x][sta]dp[j+1][x][sta]+(26num)dp[j][x][sta] 即可。

易知 n u m ≤ 2 k + 2 ≤ 9 num\leq 2k+2\leq 9 num2k+29,所以这部分我们枚举 c c c t u r n turn turn 数组转移即可。

转移的时间复杂度降低到了约 O ( 10 n k 2 2 k ) O(10nk2^{2k}) O(10nk22k)

然后能 1s AC 了。(时限 2s)

#include<bits/stdc++.h>

#define K 5
#define N 30010
#define mod 998244353

using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

namespace modular
{
	inline int add(int x,int y){if((x+=y)>=mod)x-=mod; return x;}
	inline int dec(int x,int y){if((x-=y)<0)x+=mod; return x;}
	inline int mul(int x,int y){return 1ll*x*y%mod;}
}

using namespace modular;

struct data
{
	int x,sta;
}turn[K][260][520];

int n,k,s[N];
int match[N][26];
int dp[N][K][260];
int f[K<<1],g[K<<1];
char ss[N];
bool vis[30];

int main(){
	scanf("%s%d",ss+1,&k);
	n=strlen(ss+1);
	for(int i=1;i<=n;i++) s[i]=ss[i]-'A';
	int pow2k=(1<<(k<<1)),pow2k1=pow2k<<1;
	for(int i=1;i<=n;i++)
	{
		for(int c=0;c<26;c++)
		{
			int sta=0;
			for(int l=i+k;l>=i-k;l--)
			{
				if(l<1||l>n) sta<<=1;
				else sta=sta<<1|(c==s[l]);
			}
			match[i][c]=sta;
		}
	}
	for(int x=0;x<=k;x++)
	{
		for(int sta=0;sta<pow2k;sta++)
		{
			f[k]=x;
			for(int i=k+1;i<=2*k;i++) f[i]=f[i-1]+((sta>>(i-1))&1);
			for(int i=k-1;i>=0;i--) f[i]=f[i+1]-((sta>>i)&1);
			for(int mat=0;mat<pow2k1;mat++)
			{
				for(int i=1;i<=2*k+1;i++)
				{
					if((mat>>(i-1))&1) g[i]=f[i-1]+1;
					else g[i]=max(g[i-1],f[i]);
				}
				turn[x][sta][mat].x=g[k+1]-1;
				int nsta=0;
				for(int i=2*k;i>=1;i--) nsta=nsta<<1|(g[i+1]-g[i]);
				turn[x][sta][mat].sta=nsta;
			}
		}
	}
	dp[0][k][0]=1;
	for(int i=0;i<n;i++)
	{
		for(int x=0;x<=k;x++)
		{
			for(int sta=0;sta<pow2k;sta++)
			{
				if(!dp[i][x][sta]) continue;
				int num=0;
				for(int j=max(1,i+1-k);j<=min(n,i+1+k);j++)
				{
					if(!vis[s[j]]) num++;
					vis[s[j]]=1;
				}
				int nx=turn[x][sta][0].x,nsta=turn[x][sta][0].sta;
				if(nx>=0) dp[i+1][nx][nsta]=add(dp[i+1][nx][nsta],mul(26-num,dp[i][x][sta]));
				for(int j=max(1,i+1-k);j<=min(n,i+1+k);j++)
				{
					if(!vis[s[j]]) continue;
					vis[s[j]]=0;
					nx=turn[x][sta][match[i+1][s[j]]].x,nsta=turn[x][sta][match[i+1][s[j]]].sta;
					if(nx>=0) dp[i+1][nx][nsta]=add(dp[i+1][nx][nsta],dp[i][x][sta]);
				}
			}
		}
	}
	int ans=0;
	for(int x=0;x<=k;x++)
		for(int sta=0;sta<pow2k;sta++)
			ans=add(ans,dp[n][x][sta]);
	printf("%d\n",ans);
	return 0;
}
/*
GUGUA
1
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
根据提供的引用内容,你遇到的问题是在发送HTTP POST请求时收到了403 Forbidden的错误。这个错误通常表示你没有权限访问所请求的资源。 要解决这个问题,你可以采取以下步骤: 1. 首先,确保你的请求URL正确,并且你有权限访问该URL。你可以尝试在浏览器中直接访问该URL,看看是否能够成功访问。 2. 如果你确定URL是正确的,并且你有权限访问,那么可能是你的请求中缺少了必要的身份验证信息。你可以检查你的请求头中是否包含了正确的身份验证信息,比如Token或用户名密码。 3. 另外,你还可以检查服务器端的配置,确保你的请求被正确地处理和授权。你可以查看服务器的日志,以了解更多关于403错误的详细信息。 综上所述,当你收到403 Forbidden错误时,你应该首先检查URL和权限,然后确保请求中包含了正确的身份验证信息。如果问题仍然存在,你可以进一步检查服务器端的配置和日志,以找出问题的根本原因。 #### 引用[.reference_title] - *1* [kubeadm init报错10248...(The HTTP call equal to ‘curl -sSL http://localhost:10248/healthz‘ failed)](https://blog.csdn.net/weixin_45969972/article/details/123529966)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [c/c++使用libcurl库做http客户端及封装(HTTP_GET和HTTP_POST)](https://blog.csdn.net/xsy29000/article/details/103181267)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值