【ARC100F】Colorful Sequences(动态规划)

题面

给定两个整数 n , k n,k n,k,以及一个长度为 m m m 的整数序列 a a a,其中 1 ≤ a i ≤ k 1\leq a_i\leq k 1aik

如果一个由 1 ∼ k 1\sim k 1k 组成的整数序列中,存在一个长度为 k k k 的连续子序列,满足这 k k k 个数字在子序列中都出现过,那这个序列就被称为“彩色序列”。

对于一个“彩色序列”,我们可以计算出 a a a 作为它的连续子序列的出现次数(出现位置可以部分重叠)。而你需要做的,就是计算出对于所有长度为 n n n 的彩色序列,这个出现次数的和是多少。答案对 1 0 9 + 7 10^9+7 109+7 取模。

1 ≤ m ≤ n ≤ 25000 ,   k ≤ 400 1\leq m\leq n\leq25000,~k\leq400 1mn25000, k400

题解

我知道了,动态规划题果然是最不好写题解的,稍不注意就讲得云里雾里。

首先用一个小容斥,朴素序列的出现次数和,减去非“彩色序列”的出现次数和。

朴素序列的出现次数和很简单,我们考虑在某个位置出现时,总的序列方案数。答案是 k n − m ⋅ ( n − m + 1 ) k^{n-m}\cdot(n-m+1) knm(nm+1)

接下来分情况讨论,如果 a a a 本身就是个“彩色序列”,那么非“彩色序列”中的出现次数和就是 0 。

否则,我们先求出一些有用的值来。设 d p [ i ] [ j ] dp[i][j] dp[i][j] 为长度为 i i i 、末尾一段不相同数字长度为 j j j 的非“彩色序列”个数。那么有两种转移:末尾添加一个在 [ i − j + 1 , i ] [i-j+1,i] [ij+1,i] 中未出现的数字,使 j j j 变大,或者添加一个在此区间中出现过的数字,使 j j j 分别变为 1 ∼ j 1\sim j 1j 。容易得到状态转移方程:
d p [ i ] [ j ] = ( k − j + 1 ) ⋅ d p [ i − 1 ] [ j − 1 ] + ∑ j ′ = j k − 1 d p [ i − 1 ] [ j ′ ] dp[i][j]=(k-j+1)\cdot dp[i-1][j-1]+\sum_{j'=j}^{k-1}dp[i-1][j'] dp[i][j]=(kj+1)dp[i1][j1]+j=jk1dp[i1][j]

用后缀和优化,记 s u m [ i ] [ j ] = ∑ j ′ = j k − 1 d p [ i ] [ j ′ ] sum[i][j]=\sum_{j'=j}^{k-1}dp[i][j'] sum[i][j]=j=jk1dp[i][j] ,后面会有用。

如果 a a a 中存在相等的数字,意味着出现 a a a 的序列左边和右边互不影响。我们可以令 a a a 左边最长一段互不相同的数串长度为 l l l ,右边同理,记为 r r r ,那么 a a a 在非彩色序列中的出现次数和即为
∑ i = 0 n − m s u m [ i + l ] [ l ] A ( k , l ) ⋅ s u m [ n − m − i + r ] [ r ] A ( k , r ) \sum_{i=0}^{n-m} \frac{sum[i+l][l]}{A(k,l)}\cdot\frac{sum[n-m-i+r][r]}{A(k,r)} i=0nmA(k,l)sum[i+l][l]A(k,r)sum[nmi+r][r]

除去排列数是因为 a a a 中这些位置都是确定的。

如果 m < k m<k m<k a a a 中所有数字都不同,也即是说, a a a 的左边和右边可能一起构成一个“彩色段”,那么,我们就枚举包含 a a a 的“彩色段”往左和往右分别延申多长。不难得到非彩色出现次数和为
∑ i = m n ∑ j = m k − 1 d p [ i ] [ j ] A ( k , m ) ⋅ s u m [ n − i + j ] [ j ] A ( k , j ) \sum_{i=m}^n\sum_{j=m}^{k-1}\frac{dp[i][j]}{A(k,m)}\cdot \frac{sum[n-i+j][j]}{A(k,j)} i=mnj=mk1A(k,m)dp[i][j]A(k,j)sum[ni+j][j]

具体怎么得来的可以自己算算。我也只能说“要多独立思考”之类的话了。

时间复杂度 O ( n k ) O(nk) O(nk)

CODE

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<random>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 25005
#define LL long long
#define ULL unsigned long long
#define ENDL putchar('\n')
#define DB double
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
int xchar() {
	static const int maxn = 1000000;
	static char b[maxn];
	static int pos = 0,len = 0;
	if(pos == len) pos = 0,len = fread(b,1,maxn,stdin);
	if(pos == len) return -1;
	return b[pos ++];
}
//#define getchar() xchar()
LL read() {
	LL f = 1,x = 0;int s = getchar();
	while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
	while(s >= '0' && s <= '9') {x = (x<<1) + (x<<3) + (s^48);s = getchar();}
	return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar((x%10)^48);}
void putnum(LL x) {
	if(!x) {putchar('0');return ;}
	if(x<0) putchar('-'),x = -x;
	return putpos(x);
}
void AIput(LL x,int c) {putnum(x);putchar(c);}

const int MOD = 1000000007;
int n,m,s,o,k;
int pw[MAXN];
int a[MAXN];
int c[MAXN];
int dp[MAXN][405],sm[MAXN][405];
int fac[MAXN],inv[MAXN],invf[MAXN];
int A(int n,int m) {
	if(m < 0 || m > n) return 0;
	return fac[n] *1ll* invf[n-m] % MOD;
}
int invA(int n,int m) {
	if(m < 0 || m > n) return 0;
	return invf[n] *1ll* fac[n-m] % MOD;
}
int main() {
	n = read();k = read();m = read();
	pw[0] = 1;
	for(int i = 1;i <= n;i ++) pw[i] = pw[i-1] *1ll* k % MOD;
	fac[0]=fac[1]=inv[0]=inv[1]=invf[0]=invf[1]=1;
	for(int i = 2;i <= max(n,k);i ++) {
		fac[i] = fac[i-1] *1ll* i % MOD;
		inv[i] = (MOD - inv[MOD%i]) *1ll* (MOD/i) % MOD;
		invf[i] = invf[i-1] *1ll* inv[i] % MOD;
	}
	int ct = 0,flag = 0;
	for(int i = 1;i <= m;i ++) {
		a[i] = read();
		c[a[i]] ++; if(c[a[i]] == 1) ct ++;
		if(i > k) {
			c[a[i-k]] --;
			if(c[a[i-k]] == 0) ct --;
		}
		if(ct == k) flag = 1;
	}
	int ans = pw[n-m] *1ll* (n-m+1) % MOD;
	if(flag) {
		AIput(ans,'\n');
		return 0;
	}
	dp[1][1] = k; sm[1][1] = k;
	for(int i = 2;i <= n;i ++) {
		for(int j = k-1;j > 0;j --) {
			if(j+1 < k) (dp[i][j+1] += dp[i-1][j] *1ll* (k-j) % MOD) %= MOD;
			(dp[i][j] += sm[i-1][j]) %= MOD;
		}
		for(int j = k-1;j > 0;j --)
			sm[i][j] = (sm[i][j+1] + dp[i][j]) % MOD;
	}
	int l = 0,r = 0;
	memset(c,0,sizeof(c));
	while(l < m && !c[a[l+1]]) c[a[++ l]] = 1;
	memset(c,0,sizeof(c));
	while(r < m && !c[a[m-r]]) c[a[m-r]] = 1,r ++;
	if(l < m) {
		int as = 0;
		for(int i = 0;i <= n-m;i ++) {
			int aa = i+l,bb = n-m-i+r;
			(as += (sm[aa][l]*1ll*invA(k,l)%MOD) *1ll* (sm[bb][r]*1ll*invA(k,r)%MOD) % MOD) %= MOD;
		}
		(ans += MOD-as) %= MOD;
	}
	else {
		int as = 0;
		for(int i = m;i <= n;i ++) {
			for(int j = m;j < k && j <= i;j ++) {
				(as += (dp[i][j]*1ll*invA(k,m)%MOD) *1ll* (sm[n-i+j][j]*1ll*invA(k,j)%MOD) % MOD) %= MOD;
			}
		}
		(ans += MOD-as) %= MOD;
	}
	AIput(ans,'\n');
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值