#3563 统计序列
描述
给你m个不同数字,然后用这些数字组成长度为n的序列,且保证至少有长度>=k的一段相同数字的方案数。
输入
3个整数N,M,K
输出
一行,输出的答案值MOD 1e9+7
样例输入
3 2 2
样例输出
6
提示
【数据规模】
70%:N,K<=1000
100%数据:1<=N,M,K<=1e6
思路:
显然dp
注意直接处理大于等于k的不如容斥,不方便
正难则反思想
改为处理连续长度小于K的方案数,用总情况 m^n减去即可
定义f[i][j]为到i位置前面连续长度为j个的方案数 (1<=j<=K-1)
这里状态转移最好用区间转移(单点转移不方便压维)
注意这种题定义状态虽然不难想,
但是转移的时候注意不能重复(转移的时候要“断层”,注意从简原则,并且判断重复就向后面推,如果继续更新到后面会重复讨论这个情况就重复)
(状态转移不只要注意合法性,还要恰好包含所有情况)
考虑从1~K-1个连续的长度的区间之前枚举,前后断层(防止方案重复,并且包含所有方案)
dp方程: f[i][j]= f[i-j][1 ~ K-1]*(m-1) j-- 1~K-1
显然可以把第二维压掉: f[i]= f[ i-j ]*(m-1) j-- 1~K-1
并且发现更新后面的只需要动态维护 i-K+1 ~ i-1 的总和就可以把循环压成 i 一个
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1e6+10,mod=1e9+7;
int n,m,K;
int dp[maxn];
inline int ksm(int a,int b){
int ret=1;
while(b){
if(b&1)ret=(ret*a)%mod;
a=a*a%mod;
b>>=1;
}
return ret;
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&K);
int sum=m;dp[1]=m;
for(int i=2;i<=K-1;++i){
dp[i]=(dp[i-1]*m)%mod;
sum=(sum+dp[i])%mod;
}
for(int i=K;i<=n;++i){
dp[i]=(sum*(m-1))%mod;
sum+=dp[i]-dp[i-K+1];
//只保留长度为K-2的总和
sum=(sum%mod+mod)%mod;
}
int ans=ksm(m,n);
ans=((ans-dp[n])%mod+mod)%mod;
printf("%lld",ans);
return 0;
}