虽然是简单的dp ,but真的太难想到了,而且我的码力。。。
Train Tracking 2
【题目描述】
每天特快列车都会经过农场。列车有N节车厢(1≤N≤105),每节车厢上有一个1到109之间的正整数编号;不同的车厢可能会有相同的编号。 平时,Bessie会观察驶过的列车,记录车厢的编号。但是今天雾实在太浓了,Bessie一个编号也看不见!幸运的是,她从城市里某个可靠的信息源获知了列车编号序列的所有滑动窗口中的最小值。具体地说,她得到了一个正整数K,以及N−K+1个正整数c1,…,cN+1−K,其中ci是车厢i,i+1,…,i+K−1之中编号的最小值。
帮助Bessie求出满足所有滑动窗口最小值的对每节车厢进行编号的方法数量。由于这个数字可能非常大,只要你求出这个数字对10^9+7取余的结果Bessie就满意了。
Bessie的消息是完全可靠的;也就是说,保证存在至少一种符合要求的编号方式。
【输入输出格式】
输入的第一行包含两个空格分隔的整数N和K。余下的行包含所有的滑动窗口最小值c1,…,cN+1−K,每行一个数。
输出一个整数:对每节车厢给予一个不超过109的正整数编号的方法数量对10^9+7取余的结果,满足车厢i,i+1,…,i+K−1之中编号的最小值等于ci,对于1≤i≤N−K+1均成立。
【输入输出样例】
输入
4 2
999999998
999999999
999999998
输出
3
【分析】
1:999999998 999999999 999999999 999999998
2:999999998 999999999 1000000000 999999998
3:999999998 1000000000 999999999 999999998
【解题思路】
首先你嘚想到相邻的
i
,
j
i,j
i,j如果它们的最小值不一样,那么一定可以固定一个点
eg: i i i到 i + k − 1 i+k-1 i+k−1的最小值 4 , j 4,j 4,j到 j + k − 1 j+k-1 j+k−1的最小值 3 3 3, i i i小于 j j j,即 j = i + 1 j=i+1 j=i+1,这个时候就固定了 j + k − 1 j+k-1 j+k−1得是 3 3 3
从中我们的启发是什么
把c值相同的连续区间单独拎出来
c i = c i + 1 = c i + 2 = . . . = c j = v ci=ci+1=ci+2=...=cj=v ci=ci+1=ci+2=...=cj=v
这一段代表的区间总长为 j − i + k j−i+k j−i+k,证明一下: i i i到 j j j区间长为 j − i + 1 j-i+1 j−i+1没问题
那么 j j j包含的区间长是 k k k,加在一起再减去一个重复的 j j j就是 j − i + k j-i+k j−i+k,继续
所有的数都大于等于 v v v,同时每 k k k个中就有至少一个 v v v
有一个直接的dp:设 d p [ i ] dp[i] dp[i]表示最后一个 v v v在 i i i位置时的合法方案数, p p p为 1 e 9 − v 1e9−v 1e9−v
那么有转移 d p [ i ] = ∑ ( j = i − k , i − 1 ) p i − j − 1 ∗ f [ j ] dp[i]=∑( j=i−k, i−1)p^i−j−1*f[j] dp[i]=∑(j=i−k,i−1)pi−j−1∗f[j]
这样是 O ( n k ) O(nk) O(nk)的,会TLE
稍微改变一下错位相消就变成了:
dp[i]=(p+1)*f[i−1]−p^k∗f[i−k−1]
在这里要解释一下首先如果有最小值是 x x x,那么方案数就是 ( 1 e 9 − x + 1 ) j − i + 1 (1e9-x+1)^{j-i+1} (1e9−x+1)j−i+1减去不合法的方案数
在 [ i , j ] [i,j] [i,j]区间不合法的方案数其实是所有取值全都大于了 v v v,没有一个是 v v v,其实也就是 ( 1 e 9 − x + 1 ) j − i + 1 (1e9-x+1)^{j-i+1} (1e9−x+1)j−i+1
问题是为什么要乘以 d p [ i − k − 1 ] dp[i-k-1] dp[i−k−1]呢?
你想想在
i
i
i不合法的方案数,在
i
−
1
i-1
i−1时是属于合法的方案数,所以影响
i
i
i的是
i
−
k
−
1
i-k-1
i−k−1的所有方案
因为从
i
−
k
−
1
i-k-1
i−k−1开始往前不管选什么都与
i
i
i无关,所以后面是不合法的,前面不管合不合法都算不合法,所以要相乘
接着就是考虑如何处理两个
v
v
v不一样的交界处
说白了就是两个
v
v
v谁更大,相交的部分就交给谁处理
因为这就好比不等式解集的取法,如果这个香蕉区间既要满足最小值为5又要满足最小值为6那坑定是选满足最小值为6啊!
如果处理到 i i i时, i i i有一部分区间被前面处理过,那么就要减去 k k k
因为我们是循环 i i i是每次只加 1 1 1所以当最小值不一样的时候,两个区间的香蕉部分就是 k − 1 k-1 k−1,又因为这 k − 1 k-1 k−1被前面处理过,也就意味着 i i i这个点为了满足最小值也是固定的,那么真正波动的有选择的长度就减去了 k k k
同理如果处理到 i i i时,发现 i + 1 i+1 i+1的最小值更大,也就意味着有 k − 1 k-1 k−1区间是后面处理,i也固定下来了。长度便又减去了 k k k
最后就是注意头
i
=
1
i=1
i=1和尾
i
=
n
−
k
+
1
i=n-k+1
i=n−k+1时的特判,如果
i
=
1
i=1
i=1它就不能进行把
k
−
1
k-1
k−1区间交给前面的人处理,因为它前面没人;如果
i
=
n
−
k
+
1
i=n-k+1
i=n−k+1它就不能进行把
k
−
1
k-1
k−1区间交给后面的人处理,因为它后面也没人了。
在这里,要重点解释一下下面代码的dp初值设定,早上吃饭时突然灵机乍现,明白这样写的原因
为什么?dp[0]和dp[1]初值是1 为什么?window里i循环要从2开始
1)dp[0]的情况,就是这一段区间刚好就是1~k,
那么当我们算到dp[k]的时候,它是包含了从1~k所有都取大于min的值的不合法的方案数
而我们要减掉这些不合法的方案数,也就是每一个数的取值都是min+1~1e9,有k个
所以不合法方案数就是(1e9-(min-1)+1)^k也就是代码中的tp,
那么这个系数是由dp[0]来提供,因而要赋值成1
2)dp[1]的情况,就是这一段区间刚好是1~k+1
而我们能进入这个函数一定要满足这个区间所有数的最小值是一样的val
所以当我们算到dp[k+1]的时候
不合法的方案数=2~k全都不是最小值方案数*dp[1]的方案数
那dp[1]不应该是tp吗?
no~~因为我们的通向dp式dp[i-k-1]是乘以(1e9-min)的K次方,而2到k全都不是最小值方案数是(1e9-min)的K-1次方,dp[1]的所有方案数是(1e9-min)相乘刚好就是k次方
那么这时候的系数又是由dp[1]提供的,就只能赋值成1
同样这也是为什么我们的i是从2开始循环,为了考虑这两种情况就要给0,1腾位置,
【代码实现】
#include <cstdio>
const int mod = 1e9 + 7;
const int p = 1e9;
#define MAXN 100005
#define LL long long
int n, k;
LL c[MAXN];
LL result = 1;
LL dp[MAXN];
LL qkpow ( LL x, LL y ) {
LL res = 1;
while ( y ) {
if ( y % 2 ) res = ( res * x ) % mod;
x = ( x * x ) % mod;
y >>= 1;
}
return res % mod;
}
LL window ( LL val, LL len ) {
LL tmp = p - val;
LL tp = qkpow ( tmp, k );
dp[0] = dp[1] = 1;
for ( int i = 2;i <= len + 1;i ++ ) {
dp[i] = ( ( tmp + 1 ) * dp[i - 1] ) % mod;
if ( i - k - 1 >= 0 )
dp[i] = ( ( dp[i] - ( ( tp * dp[i - k - 1] ) % mod ) + mod ) % mod ) % mod;
}
return dp[len + 1] % mod;
}
int main() {
scanf ( "%d %d", &n, &k );
for ( int i = 1;i <= n - k + 1;i ++ )
scanf ( "%lld", &c[i] );
for ( int i = 1, j;i <= n - k + 1;i = j + 1 ) {
LL len;
j = i;
while ( c[i] == c[j + 1] )
j ++;
len = j - i + k;
if ( i != 1 && c[i - 1] > c[i] )
len -= k;
if ( j != n - k + 1 && c[j + 1] > c[i] )
len -= k;
if ( len > 0 )
result = ( result * window ( c[i], len ) ) % mod;
}
printf ( "%lld", result % mod );
return 0;
}