P13382 解题报告
前言
连续段 DP 板子,问题在于没有学连续段 DP 并理解其本质
这个题目还可以当成每一次插入一些字母是一个很好的 trick
同时记得联考没有好东西,不会就跳了看后面的暴力拿满
经常出现 T3,4 没有时间做的情况也可以解决,尝试第一个小时先看完题目并且拿到最高的部分分再考虑 T1,2 虽然会损失一个小时但是至少可以把暴力分打满
题意
给出一个字符串求有多少种重排方案使得字符串的极长连续段数量为 \(S\)。
$ len\le 10^6,S\le 100 $。
思考
首先联考的时候考了 1h 突然说少了一个 \(S\le 100\) 的条件,然后引起公愤。
那么发现计数类问题的大部分做法都是 DP,或者说发现这个题目不能使用调整法找答案,所以考虑 DP。
- 思考过程1
首先 DP 只是搜索带上记忆化而已,所以考虑如果搜索要记录什么东西,最基本的搜索是记录每一个字母还剩下多少个,发现这个东西在 DP 里面不好记录,所以考虑换一种搜索方式,发现可以从最小的字母到最大的字母依次放入这个字符串,这样就只需要考虑他放在了两个串的中间还是放在了一个串的内部了
但是发现还有一个问题,如果我们对于一个字符有很多个怎么办,这样的话可能会互相影响,所以同时插入即可,设 \(dp_{i,j}\) 表示前 \(i\) 个字母有 \(j\) 个连续段,有多少种方案,对于每一个字母,假设分别有 \(a,b\) 个放在了两个串的中间/一个串的内部,则转移方程就能推出来了和第二种思考过程的转移式一样所以放在一起 - 思考过程2
本人的考场思维
首先如果对连续段 DP 熟悉的话就会马上想到连续段 DP,既然是依次插入,那么可以设置一个插入顺序,因为他只有 26 个字母,所以显然是按照字母插入,那么现在有 \(j\) 个段,那么 \(j+1\) 个位置是两个段的中间,\(总字母数 - j\) 个中间位置可以插入,插在中间位置是 \(+2\),反之 \(+1\),所以可以设计出转移方程
\[dp_{i,j} \times {j+1\choose a}\times {sum_{i-1}\choose b}\times {siz_{i}-1\choose a+b-1}\to dp_{i+1,j+a+2\times b}
\]
代码
#include<algorithm>
#include<iostream>
#include<cstring>
#include<climits>
#include<stdio.h>
#include<cmath>
#include<time.h>
#define ll long long
using namespace std;
const int N=29,M=109,K=1e6+9;
const ll mod=1e6+3;
int dp[N][M],siz[N],S,n,sum[N];
string src;
ll fac[K],inv[K];
inline void gx(int &x,int y){
x=x+y;
if(x>mod)x-=mod;
}
inline ll C(int x,int y){
// cout<<x<<" choose "<<y<<endl;
return fac[x]*inv[x-y]%mod*inv[y]%mod;
}
//x choose y
inline void solve(int T){
cin>>src;n=src.size();
S=1;
memset(siz,0,sizeof(siz));
for(int i=1;i<n;i++){
S+=(src[i]!=src[i-1]);
siz[src[i-1]-'a'+1]++;
}
siz[src[n-1]-'a'+1]++;
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=1;i<=26;i++){
sum[i]=sum[i-1]+siz[i];
if(siz[i]==0){
for(int j=0;j<=S;j++)
dp[i][j]=dp[i-1][j];
continue;
}
for(int j=0;j<=S;j++){
for(int a=0;a<=min(S,siz[i]);a++)
for(int b=0;b<=min(S/2,siz[i]);b++){
if(a==0 && b==0) continue;
if(a+b>siz[i]) continue;
if(j+a+2*b>S) continue;
if(sum[i-1]-j<b || j+1<a) continue;
gx(dp[i][j+a+2*b],dp[i-1][j]*C(sum[i-1]-j,b)%mod*C(j+1,a)%mod*C(siz[i]-1,a+b-1)%mod);
// cout<<i<<' '<<j<<' '<<i-1<<' '<<j-a-2*b<<endl;
}
// cout<<i<<' '<<j<<" : "<<dp[i][j]<<endl;
}
}
cout<<"Case #"<<T<<": "<<dp[26][S]<<endl;
}
inline ll ksm(ll x,ll y){
ll res=1;
while(y){
if(y&1)res=res*x%mod;
x=x*x%mod;y>>=1;
}
return res;
}
inline void init(){
fac[0]=1;
for(int i=1;i<mod;i++)
fac[i]=fac[i-1]*i%mod;
inv[mod-1]=ksm(fac[mod-1],mod-2);
for(int i=mod-1;i>=1;i--)
inv[i-1]=inv[i]*i%mod;
}
int main(){
// freopen("runs.in","r",stdin);
// freopen("runs.out","w",stdout);
clock_t st,ed;
st=clock();
int T;
cin>>T;
init();
for(int t=1;t<=T;t++){
solve(t);
}
ed=clock();
// cout<<double(ed-st)/1000<<" s";
return 0;
}

浙公网安备 33010602011771号