• 【AT4169】[ARC100D] Colorful Sequences(DP)


    点此看题面

    • 给定(n,k)和一个长为(m)的值在([1,k])范围内的序列(a)
    • 定义一个序列是好的,当且仅当它长度为(n)、所有值在([1,k])范围内且存在一个长度为(k)的子串是(1sim k)的一个排列。
    • 求所有好序列中(a)的出现次数之和。
    • (mle nle2.5 imes10^4,kle400)

    容斥

    发现存在一个长度为(k)的排列的限制有点烦人,因此我们容斥,用总方案数减去非法方案数。

    长度已知为(n),求的又是出现次数之和,我们可以分别考虑(a)以每个位置为开头的贡献,而其余位置都可以任填,因此就是((n-m+1) imes k^{n-m})

    非法方案数就需要大力分类讨论。

    (a)中包含(1sim k)的排列

    直接在(a)中暴枚起点向后枚举(k)位即可判断是否存在一个排列。

    如果存在,显然含(a)的序列都是合法的,也就是说非法方案数为(0),直接输出总方案数即可。

    (a)中包含重复的元素

    也就是说不可能存在一个排列包含(a),因此我们可以分别考虑(a)的左右两端。

    (f_{i,j})表示一个长度为(i)的非法序列,后缀极长排列长度为(j)的方案数。

    转移就考虑填入一个不在排列中的数使排列长度加(1),或填入排列中的一个数,可以使新的排列变成(1sim j)中的任意一种长度。

    因此得出转移方程:

    [f_{i,j}=f_{i-1,j-1} imes(k-(j-1))+sum_{x=j}^{k-1}f_{i-1,x} ]

    后面的(sum)只要我们从大到小枚举(j)同时维护(f_{i-1,j})的后缀和即可优化转移。

    然后我们假设(a)的前缀极长排列长度为(x),后缀极长排列长度为(y)

    并上前后部分,交界处的极长排列长度分别可以是([x,k))([y,k))中的一个数。

    因此在(a)前面放一个长度为(i)的序列使得拼成序列仍然非法的方案数为:(因为最后长为(x)的排列填数已知,要除以一个排列数)

    [frac{sum_{j=x}^{k-1}f_{i+x,j}}{A_k^x} ]

    不妨令(s1_i=sum_{j=x}^{k-1}f_{i,j}),同理设(s2_i=sum_{j=y}^{k-1}f_{i,j})

    然后只要按照先前提到过的方法,枚举(a)的开头位置,则前后要放的序列长度都已知,直接利用(s1)(s2)两个数组计算即可。

    最后给答案除以(A_k^x imes A_k^y)

    (a)中不含重复的元素

    此时的(a)相当于是一个长度为(m)的排列。

    则我们直接求出所有长度为(m)的排列的贡献,然后只要给它除以一个(A_k^m)即可。

    仿照(f_{i,j}),设(g_{i,j})表示一个长度为(i)的非法序列,后缀极长排列长度为(j)的所有方案下,长度为(m)的排列出现次数之和。

    转移方程也是类似的,只是要考虑上在这个位置添上排列的方案数,即:

    [g_{i,j}=g_{i-1,j-1} imes(k-(j-1))+sum_{x=j}^{k-1}g_{i-1,x}+[jge m]f_{i,j} ]

    那么非法方案数就应该是(frac{sum_{j=1}^{k-1}g_{n,j}}{A_k^m})

    代码:(O(nk))

    #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 25000
    #define K 400
    #define X 1000000007
    using namespace std;
    int n,m,k,a[N+5],p[K+5],Fac[N+5],IFac[N+5],s1[N+5],s2[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;}
    namespace FastIO
    {
    	#define FS 100000
    	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
    	char oc,FI[FS],*FA=FI,*FB=FI;
    	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
    	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    }using namespace FastIO;
    int f[N+5][K+5];I void DP_f()//动态规划求解f数组
    {
    	f[1][1]=k;for(RI i=2;i<=n;++i) for(RI j=k-1,t=0;j;--j)
    		t=(t+f[i-1][j])%X,f[i][j]=(1LL*f[i-1][j-1]*(k-(j-1))+t)%X;
    }
    int g[N+5][K+5];I void DP_g()//动态规划求解g数组
    {
    	g[1][1]=k*(m==1);for(RI i=2;i<=n;++i) for(RI j=k-1,t=0;j;--j)
    		t=(t+g[i-1][j])%X,g[i][j]=(1LL*g[i-1][j-1]*(k-(j-1))+t)%X,j>=m&&(g[i][j]=(g[i][j]+f[i][j])%X);//算上这个位置添上排列的方案数
    }
    int main()
    {
    	RI i,j,tg=1;for(read(n,k,m),i=1;i<=m;++i) read(a[i]),p[a[i]]?tg=0:p[a[i]]=1;
    	for(Fac[0]=IFac[0]=i=1;i<=max(m,k);++i) Fac[i]=1LL*Fac[i-1]*i%X,IFac[i]=QP(Fac[i],X-2);
    	RI o=1LL*(n-m+1)*QP(k,n-m)%X,t=0;for(i=1;i+k-1<=m;++i)//枚举长度为k排列的起始位置
    		{for(j=1;j<=k&&p[a[i+j-1]]^(-i);++j) p[a[i+j-1]]=-i;if(j>k) return printf("%d
    ",o),0;}//如果存在,直接输出总方案数
    	#define IA(x) (1LL*IFac[k]*Fac[k-(x)]%X)//A(k,x)的逆元
    	if(DP_f(),tg) {for(DP_g(),i=1;i^k;++i) t=(t+g[n][i])%X;return printf("%d
    ",(o-1LL*t*IA(m)%X+X)%X),0;}//如果本身是排列,DP出g计算
    	RI x;for(x=1;p[a[x]]^2;++x) p[a[x]]=2;for(x=x-1,i=1;i<=n;++i) for(j=x;j^k;++j) s1[i]=(s1[i]+f[i][j])%X;//前缀最长排列x
    	RI y;for(y=m;p[a[y]]^3;--y) p[a[y]]=3;for(y=m-y,i=1;i<=n;++i) for(j=y;j^k;++j) s2[i]=(s2[i]+f[i][j])%X;//后缀最长排列y
    	for(i=0,j=n-m;~j;++i,--j) t=(1LL*s1[i+x]*s2[j+y]+t)%X;return printf("%d
    ",(o-1LL*t*IA(x)%X*IA(y)%X+X)%X),0;//枚举A的起始位置计算方案数
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    Spring Bean(一)
    Spring IOC(一)
    三种常用的查找算法
    分布式架构的一致性
    Java内存区域笔记(一)
    分组交换和电路交换
    Python说文解字_Python之多任务_03
    Python说文解字_Python之多任务_02
    Python说文解字_Python之多任务_01
    Python说文解字_杂谈09
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/AT4169.html
Copyright © 2020-2023  润新知