统计重复计数 题解

题目跳转:P10976 统计重复个数

题目大意

给定 \(s1\),\(n1\),\(s2\),\(n2\) 定义 \([str,n]\) 表示 \(n\) 个 \(str\) 头尾相接,求最大的 \(m\) 满足 \([s2,n2\times m]\) 是 \([s1,n1]\) 的子序列。

思路分析

这是一道倍增优化 \(dp\) 的好题。让我们一步一步来,从最简单的开始。

首先发现 \(str=[str_2,m]=[[s2,n2],m]=[s2,m\times n2]\)。

那么 \(str\) 就是 \(s2\) 复制了 \(m\times n2\) 遍。

设 \(dp_{i,j}\) 表示从 \(s1_i\) 开始,往后多少个字符能生成 \([s2,j]\)

那么转移也就出来了(下面不理解的先在纸上画个图,对着上面的状态设计多看几遍就懂了):

\[dp_{i,j}=dp_{i,1}+dp_{i+dp_{i,1},j-1}=dp_{i,2}+dp_{i+dp_{i,2},j-2}=\cdot\cdot\cdot \]

其中:

\[0\leq i \lt \lvert s1\rvert\\ \]

原始状态中 \(j\) 最大需要达到 \(m\times n2\),但 \(m\) 是待求答案,不能直接作为数组范围使用。题目约束为:

\[1\leq\lvert s1\rvert,\lvert s2\rvert\leq 100\\ 0\leq n1,n2 \leq 10^6 \]

答案 \(m\) 可能达到 \(10^8\) 量级。这样肯定是不行的,不仅会 \(TLE\) 还开不下 \(dp\) 数组。

我们再次观察状态转移:

\[dp_{i,j}=dp_{i,1}+dp_{i+dp_{i,1},j-1}=dp_{i,2}+dp_{i+dp_{i,2},j-2}=\cdot\cdot\cdot \]

发现里面有许多无用的状态,这时候就可以用倍增来优化。

再次设 \(dp_{i,j}\) 表示从 \(s1_i\) 开始,往后多少个字符能匹配出 \([s2,2^j]\) 作为子序列。

先来说说原理:

给你任意一个正整数 \(x\),都可以用若干个 \(2\) 的次方数相加得到。例如:

\[4=2^2\\ 10=2^1+2^3=2+8\\ 23=2^0+2^1+2^2+2^4=1+2+4+16 \]

解释起来也非常简单,就是把 \(x\) 写成二进制,\(2\) 的次方数正好对应 \(x\) 的二进制上为 \(1\) 的位置。

那么回到这个题,状态设计好了,转移也就简单了:

\[dp_{i,j}=dp_{i,j-1}+dp_{(i+dp_{i,j-1})\bmod\lvert s1\rvert,j-1} \]

求答案的时候从大到小枚举倍增指数 \(k\),设当前已经消耗的字符数为 \(pos\),初始 \(pos=0\)。如果

\[pos+dp_{pos\bmod\lvert s1\rvert,k}\leq \lvert s1\rvert\times n1 \]

也就是还在 \(str_1\) 的范围内,那就把 \(2^k\) 计入已经匹配的 \(s2\) 个数,同时更新:

\[pos=pos+dp_{pos\bmod\lvert s1\rvert,k} \]

最终的答案要再除以 \(n2\)。

为什么要从大到小枚举 \(j\) 呢?看下面这个例子:

从大到小:

\[8\geq 2^3=8 \]

从小到大:

\[8\geq 2^0+2^1+2^2=1+2+4=7 \]

枚举顺序不同造成了答案错误。

现在还要再考虑 \(dp_{i,0}\) 怎么求,观察到 \(1\leq\lvert s1\rvert,\lvert s2\rvert\leq 100\),所以直接暴力枚举就行(具体看代码),这里要注意内层枚举位置 \(i\) 的范围是:\(i\lt min(\lvert s1\rvert\times n1,p+\lvert s1\rvert\times \lvert s2\rvert)\),不然会超时。

时间复杂度:\(O(\lvert s1\rvert^2\times\lvert s2\rvert+\lvert s1\rvert\times log\space m)\)

注意:

  1. 多测要清空。
  2. 要注意 \(dp\) 数组的第一维在枚举时会超出 \(\lvert s1\rvert\),所以记得取模。
  3. 因为 \(\lvert s2\rvert\times n2\leq 10^8\),所以倍增指数最大约到 \(log_210^8\approx 27\),\(dp\) 数组第二维应至少开到 \(28\)。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
string s1,s2;
ll n1,n2,dp[105][28],ans;
int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	while(cin>>s2>>n2>>s1>>n1){
		ll n=s1.size(),m=s2.size();
		for(ll i=0;i<n;i++){
			for(ll j=0;j<=27;j++){
				dp[i][j]=1e18;
			}
		}
		for(ll p=0;p<n;p++){
			for(ll i=p,j=0;i<min(n*n1,p+m*n)&&j<m;i++){
				if(s1[i%n]==s2[j]){
					j++;
				}
				if(j==m){
					dp[p][0]=i-p+1;
					break;
				}
			}
		}
		for(ll j=1;j<=27;j++){
			for(ll i=0;i<n;i++){
				dp[i][j]=min(dp[i][j],dp[i][j-1]+dp[(i+dp[i][j-1])%n][j-1]);
			}
		}
		ans=0;
		for(ll i=27,j=0;i>=0;i--){
			if(j+dp[j%n][i]<=n*n1){
				j+=dp[j%n][i];
				ans+=(1<<i);
			}
		}
		cout<<ans/n2<<"\n";
	}
	return 0;
}
posted @ 2026-11-05 10:02  beiciqi  阅读(7)  评论(0)    收藏  举报