- 有一个值域为\([1,10^9]\)的整数序列,给出每相邻\(k\)个元素中的最小值,求原序列可能的个数。
- \(n\le10^5\)
\(a_i\)全相同时的\(DP\)
方便起见记\(w=10^9-a_i\)。
那也就是说,至多隔\(k\)个位置选择一次\(a_i\),而不选\(a_i\)的时候有\(w\)种选法。
因此设\(f_i\)表示第\(i\)个位置选择了的方案数,得出暴力转移式:
\[f_i=\sum_{j=i-k}^{i-1}f_j\times w^{i-j-1} \]我们发现转移的系数是\(w^{i-j-1}\),因此考虑把\(f_{i-1}\)乘上\(w\),发现:
\[f_{i-1}\times w=\sum_{j=i-k-1}^{i-2}f_j\times w^{i-j-1} \]它和\(f_i\)只有\(f_{i-k-1}\)和\(f_{i-1}\)两项不同,稍微补正一下即可:
\[f_i=f_{i-1}\times (w+1)-f_{i-k-1}\times w^k \]这样就可以\(O(n)\)去\(DP\)了。
\(a_i\)不相同时的分段拆解
对于\(a_i\)全相同的一段\([i,j]\),粗略考虑它在原序列中管辖的区间为\([i,j+k-1]\),长度为\(j-i+k\)。
但是,如果\(a_{i-1}>a_i\),说明\(s_{i+k-1}=a_i\),且\(s_{i\sim i+k-2}\)肯定全都大于等于\(a_{i-1}\),因此当前段管辖区间大小减少\(k\)。
同理,如果\(a_{j+1}>a_i\),说明\(s_j=a_i\),且\(s_{j+1\sim j+k-1}\)肯定全都大于等于\(a_{j+1}\),因此当前段管辖区间大小减少\(k\)。
然后发现不同的管辖区间之间是独立的,分别按照前面的方法\(DP\)然后把方案数相乘即可。
代码:\(O(n)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define INF 1000000000
#define X 1000000007
using namespace std;
int n,k,a[N+5];I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
int f[N+5];I int DP(CI x,CI n)//动态规划
{
RI i,w=INF-x,p=QP(w,k);f[0]=f[1]=1;//初始化
for(i=2;i<=n+1;++i) f[i]=(1LL*f[i-1]*(w+1)-(i>k?1LL*f[i-k-1]*p%X:0)+X)%X;return f[n+1];//利用f[i-1]*w高效转移
}
int main()
{
RI i,j,p;for(scanf("%d%d",&n,&k),i=1;i<=n-k+1;++i) scanf("%d",a+i);
RI t=1,o;for(i=1;i<=n-k+1;i=j+1) {j=i;W(a[j+1]==a[i]) ++j;o=j-i+k-(a[i-1]>a[i])*k-(a[j+1]>a[i])*k,t=1LL*t*DP(a[i],o)%X;}//根据a[i]分段拆解
return printf("%d\n",t),0;
}