把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷5204】[USACO19JAN] Train Tracking 2 P(DP)

点此看题面

  • 有一个值域为\([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;
}
posted @ 2021-05-31 07:49  TheLostWeak  阅读(45)  评论(0编辑  收藏  举报