[USACO19JAN]Train Tracking 2

题面

传送门 to luogu

题目描述
每天特快列车都会经过农场。列车有 N N N 节车厢 ( 1 ≤ N ≤ 1 0 5 ) (1≤N≤10^5) (1N105) ,每节车厢上有一个 1 1 1 1 0 9 10^9 109 之间的正整数编号;不同的车厢可能会有相同的编号。 平时, B e s s i e \tt{Bessie} Bessie 会观察驶过的列车,记录车厢的编号。但是今天雾实在太浓了, B e s s i e \tt{Bessie} Bessie 一个编号也看不见!幸运的是,她从城市里某个可靠的信息源获知了列车编号序列的所有滑动窗口中的最小值。具体地说,她得到了一个正整数 K K K ,以及 ( N − K + 1 ) (N−K+1) (NK+1) 正整数 c 1 , … , c N + 1 − K c_1,…,c_{N+1−K} c1,,cN+1K ,其中 c i c_i ci 是车厢 i , ( i + 1 ) , … , ( i + K − 1 ) i,(i+1),…,(i+K−1) i,(i+1),,(i+K1) 之中编号的最小值。

帮助 B e s s i e \tt{Bessie} Bessie 求出满足所有滑动窗口最小值的对每节车厢进行编号的方法数量。由于这个数字可能非常大,只要你求出这个数字对 1 0 9 + 7 10^9+7 109+7 取余的结果 B e s s i e \tt{Bessie} Bessie 就满意了。

B e s s i e \tt{Bessie} Bessie 的消息是完全可靠的;也就是说,保证存在至少一种符合要求的编号方式。

输入格式
输入的第一行包含两个空格分隔的整数 N N N K K K 。余下的行包含所有的滑动窗口最小值 c 1 , … , c N + 1 − K c_1,…,c_{N+1−K} c1,,cN+1K ,每行一个数。

输出格式
输出一个整数:对每节车厢给予一个不超过 1 0 9 10^9 109 的正整数编号的方法数量,对 1 0 9 + 7 10^9+7 109+7 取余的结果。

思路

为了方便叙述,首先将想要求得的车厢编号设为数组 a a a

很明显,这是一道组合数学的题。那么有没有一些 a a a 值是可以确定的呢?

获得定值 a a a

考虑两个相邻而不同的 c c c 值:

情况一: c i < c i + 1 c_i<c_{i+1} ci<ci+1
那么, a i = c i a_i=c_i ai=ci 。理由: ∀ j ∈ [ i + 1 , i + k ] \forall j\in[i+1,i+k] j[i+1,i+k] ,有 a j ≥ c i + 1 > c i a_j≥c_{i+1}>c_i ajci+1>ci 。也就是说, c i c_i ci 所包含的区间内,除了 a i a_i ai ,每个数都是 大于(两个 c c c 值无法取等!) c i c_i ci 的。又因为 c i c_i ci 一定是某个 a a a 值,就有了该结论。

情况二: c i > c i + 1 c_i>c_{i+1} ci>ci+1
类似的,有 a i + k = c i + 1 a_{i+k}=c_{i+1} ai+k=ci+1

考虑不定值 a a a

如果 a a a 值不定,怎么算总方案?此时肯定 c c c 值相同。考虑区间 [ l , r ] [l,r] [l,r] ,假设 需要考虑(注意该措辞)的长度为 l e n len len ,最小值为 x x x ,是可以用动态规划计算的。

d p l e n dp_{len} dplen 表示长度为 l e n len len 时的方案数。令 y y y 为每一位上能填的最大的数(也就是题意中的 1 0 9 10^9 109),则有

d p i = { d p i − 1 × ( y − x + 1 ) ( i < k ) d p i − 1 × ( y − x + 1 ) − ( y − x ) k ( i = k ) d p i − 1 × ( y − x + 1 ) − d p i − k − 1 × ( y − x ) k ( i > k ) dp_i=\begin{cases}dp_{i-1}\times(y-x+1) &(i<k)\\ dp_{i-1}\times(y-x+1)-(y-x)^k &(i=k)\\ dp_{i-1}\times(y-x+1)-dp_{i-k-1}\times(y-x)^k &(i>k)\\ \end{cases} dpi=dpi1×(yx+1)dpi1×(yx+1)(yx)kdpi1×(yx+1)dpik1×(yx)k(i<k)(i=k)(i>k)

解释一波:当 i < k i<k i<k 时,只要 a a a 值不小于 x x x 就行了。当 i = k i=k i=k 时,每一位都是随便填 [ x , y ] [x,y] [x,y] 之间的数,但是要减去这 k k k 个值每个都大于 x x x 的情况。

i > k i>k i>k 时,较为复杂:

  • 首先,前 ( i − 1 ) (i-1) (i1) a a a 值已经满足条件,最后一个值至少不能比 x x x 小,乘法原理 d p i − 1 × ( y − x + 1 ) dp_{i-1}\times(y-x+1) dpi1×(yx+1)
  • 可是加入了第 i i i 个后,就得检查包含第 i i i 位的区间的正确性。
  • 而新区间 ( i − k , i ] (i-k,i] (ik,i] 出现“问题”的情况有多少种?前 ( i − k − 1 ) (i-k-1) (ik1) a a a 值已经搞定(因为算的是“首先”中不符题意的,而“首先”中前 ( i − 1 ) (i-1) (i1) 个都搞定了,只有可能是新区间出问题),而最后 k k k 个值都大于 x x x ,乘法原理 d p [ i − k − 1 ] × ( y − x ) k dp[i-k-1]\times(y-x)^k dp[ik1]×(yx)k 。最后做差。

那么需要考虑多长的区间?首先,这个区间本身有 ( r − l + k ) (r-l+k) (rl+k) 这么长( r − l + 1 r-l+1 rl+1 区间长度+ ( k − 1 ) (k-1) (k1) r r r 位的滑动窗口带来的额外长度)。其中有一些值是被固定了的:

情况一: c l − 1 > c l c_{l-1}>c_l cl1>cl

这会导致 a l + k − 1 a_{l+k-1} al+k1 为定值。也就是说,前面的 [ l , l + k − 1 ] [l,l+k-1] [l,l+k1] 都不用 在此时(考虑是必要的)考虑。那么就要减去 k k k 的长度。

情况二: c r < c r + 1 c_r<c_{r+1} cr<cr+1

这会导致 a r a_r ar 为定值。也就是说,后面的 [ r , r + k − 1 ] [r,r+k-1] [r,r+k1] 都不用 在此时(知道我想说什么吧?)考虑。那么就要减去 k k k 的长度。

减成非正数怎么办?不用担心。这意味着整个区间都不用被考虑,通俗点,只有一种方法:什么也不干。所以只有在 l e n > 0 len>0 len>0 时才调用 d p dp dp

对了,前面的 o r or or 后面的,未在此时考虑的,有没有被考虑呢?假设前面的没被考虑,有 c l − 1 > c l c_{l-1}>c_l cl1>cl ,则当初 r = l − 1 r=l-1 r=l1 的时候,一定不满足条件二(也就是不考虑后面的),就考虑了该区间。其实你同时也会感觉到,这样做也是不重复的。

这时候,填坑!——区间长度 i < k i<k i<k 的时候,是怎么一回事?——说白了,就是,你随便填,只要不小于规定的最小值,总有其他 a a a 值帮你搞定。那么什么也不用减!

复杂度计算

不断找相同的区间,一段一段跳跃,计算长度 Θ ( 1 ) \Theta(1) Θ(1) ,计算 d p dp dp O ( l e n ) \mathcal O(len) O(len) ,由于是不重复的, l e n len len 之和必为 n n n ,所以加起来总和为 O ( n ) \mathcal O(n) O(n)

代码

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;

const int MAXN = 100000;
const int MAXNUM = MAXN*10000;
const int MOD = MAXNUM+7;
inline int qkpow(long long base,int q){
	long long ans = 1; base %= MOD;
	for(; q; q>>=1, base=base*base%MOD)
		(q&1) and (ans = ans*base%MOD);
	return ans;
}
int dp[MAXN+2] = {1}, n, k, c[MAXN+2];

int get_dp(int x,int len){
	int tmp = qkpow(MAXNUM-x,k); // (y-x)^k
	for(int i=1,MAXI=min(len,k); i<=MAXI; ++i)
		dp[i] = dp[i-1]*(MAXNUM-x+1ll)%MOD;
	if(len<k) return dp[len];
	dp[k] = (dp[k]-tmp+MOD)%MOD;
	for(int i=k+1; i<=len; ++i){
		dp[i] = (dp[i-1]*(MAXNUM-x+1ll)-1ll*dp[i-k-1]*tmp)%MOD;
		if(dp[i]<0) dp[i] += MOD;
	}
	return dp[len];
}
int main()
{
	scanf("%d %d",&n,&k);
	for(int i=1; i<=n-k+1; ++i)
		scanf("%d",&c[i]);
	int l = 1, r, ans = 1;
	while(l <= n-k+1)
	{
		r = l;
		while(r != n-k+1 and c[r+1] == c[l])
			++ r;
		int len = r-l+k;
		if(l != 1 and c[l-1] > c[l])
			len -= k;
		if(r != n-k+1 and c[r] < c[r+1])
			len -= k;
		if(len > 0)
			ans = 1ll*ans*get_dp(c[l],len)%MOD;
		l = r+1;
	}
	printf("%d",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值