P4591 [TJOI2018] 碱基序列,详细题解

一道“字符串hash + dp”的好题

一.题意简述

题面理解起来有点困难,翻译一下题面
给定 DNA 串 \(s\) 和 \(k\) 组字符串,从每组恰好选一个字符串,按组顺序拼接。要求拼接结果是 \(s\) 的子串,求所有选择方案在 \(s\) 中出现的总次数(同一方案能在多个位置出现会分别计数)。
答案对 \(10^9+7\) 取模。
数据范围: \(1\le k\le 100,1\le |s|\le 10000,1\le a_i\le 10\)

二.思路

由于是字符串之间的匹配,数据范围不小,所以我们选择使用 “字符串hash” 的方法来 \(O(1)\) 高效判断。
我们不难发现:当处理到第 \(i\) 组碱基序列时,它对答案的贡献可以通过 \(i-1\) 组碱基序列转移而来,考虑 dp

1.状态定义

我们定义 \(dp[i][r]\) 表示处理完第 \(i\) 组碱基序列后,拼接结果恰好结束在 \(s\) 的第 \(r\) 个位置的方案数

  • \(i\) 表示已经处理了几组
  • \(r\) 表示当前最后一个字符在 \(s\) 中的位置 \((1\sim len)\)
  • 含义是:前 \(i\) 组选出的字符串收尾相邻地拼接起来后,其最后一个字符落在 \(s[r]\)

2.初始化

for(int i=0;i<=10000;i++)dp[0][i]=1;

dp[0][r]=1表示:还没选任何字符串时,可以从 s 的任意位置之后开始
如果没有初始化,第一段就只能从 s 的开头开始匹配,得到的是前缀匹配而非任意位置匹配

3.转移

for(int l=1,r=lx;l<=len and r<=len;r++,l++){
	int key=(hs[r]+mod-hs[l-1]*mi[r-l+1]%mod)%mod;
	if (key!=tar)continue;
	dp[i][r]+=dp[i-1][l-1];
	dp[i][r]%=mod;
}

核心思想是拼接:

  • 前 i-1 组拼好后,最后一个字符落在 s[l-1]
  • 第 i 组选出的串 x 恰好等于 s[l...r]
  • 那么前 i 组拼好后就落在 s[r]

4.答案统计

for(int i=1;i<=len;i++){
	ans+=dp[k][i]%mod;
}

处理完所有 k 组碱基序列后,把结束在 s 各个位置的方案数都加起来,别忘了对 \(10^9+7\) 取模

三.实现代码

题目传送门
没想到这竟然是道紫题

#include <bits/stdc++.h>
using namespace std;
#define int unsigned long long
const int mod=1e9+7;
const int N=1e4+5;
int k,hs[N],mi[N],dp[105][N],ans;
string s;
signed main(){
	for(int i=0;i<=10000;i++)dp[0][i]=1;
	mi[0]=1;
	cin>>k>>s;
	int len=s.size();
	s=" "+s;
	for(int i=1;i<=len;i++){
		mi[i]=mi[i-1]*127%mod;
		hs[i]=(hs[i-1]*127+s[i])%mod;
	}
	for(int i=1;i<=k;i++){
		int a;
		cin>>a;
		for(int j=1;j<=a;j++){
			string x;
			cin>>x;
			int tar=0,lx=x.size();
			x=" "+x;
			for(int u=1;u<=lx;u++){
				tar=(tar*127+x[u])%mod;
			}
			for(int l=1,r=lx;l<=len and r<=len;r++,l++){
				int key=(hs[r]+mod-hs[l-1]*mi[r-l+1]%mod)%mod;
				if (key!=tar)continue;
				dp[i][r]+=dp[i-1][l-1];
				dp[i][r]%=mod;
			}
		}
	}
	for(int i=1;i<=len;i++){
		ans+=dp[k][i]%mod;
	}
	cout<<ans%mod;
	return 0;
}
posted @ 2026-10-26 16:37  Emoji-happy  阅读(32)  评论(0)    收藏  举报