插入类DP

对于这类题目的特征:

1.排列性质
2.100n5×103
3.答案和排列的上升下降的突变点有关

套路:

按照某个从小到大的顺序插入,有了这个顺序就能算贡献或者方案数
贡献往往提前计算,存在每个元素有新开一段、合并两端、连在某一段后面的不同方案
有的允许了A,W,V,M 形状不同的最终状态可能在前面是同一状态,每个状态是为后来的状态做准备的

新开一个段连在某个段的边上并非同一个状态,新开一个段,未来会在两端之间插入更大的数字来合并两个 段,连在一个段的边上,则未来就不会如此

关于端点:

1.如果固定了端点,往往是当 i 是左右端点时特判
2.如果没有固定左右端点,往往多加一维记录当前确定了几个端点(如果左右端点的转移/贡献相同),或者多加两维记录当前确定了左/右端点(如果左右端点的转移/贡献并不相同)

QY太强了,令我的总结旋转


按照结尾数字排名进行的插入类DP

A. 【Atcoder_DP】T-排列 (AI)

有一个长度为N 正整数排列,再给一个由 <> 组成的长为 N1 的字符串
对于任意满足 1iN1的字符 si ,如果 si<Pi<Pi+1 、 如果 si>Pi>Pi+1 。求满足这样性质的排列 P 的方案数
N3000

考虑 DP ,设 f[i][j] 表示对于前 i 个数字,最后一个数字在前 i 个数字里排名第 j 的方案数
显然可以列出递推式

s[i1]==<:f[i][j]=k=1j1f[i1][k]s[i1]==>:f[i][j]=k=ji1f[i1][k]

用前缀和优化,就可以把时间复杂度优化到O(n2)

#include"bits/stdc++.h"

using namespace std;
constexpr int N=3015,P=1e9+7;
#define inl inline
#define regi register
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
//#define ll long long
//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
//inl int add_(int a,int b,int p=P){return a+b>=p?a+b-p:a+b;}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}

inl int read(void)
{
	int x=0;short f=1;char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;
	for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
	return x*f;
}

int f[N][N],sum[N][N];
int n;int ans;
char ch[N];

int main(void)
{
	n=read();
	cin>>ch+1;
	for(int i=1;i<=n;i++)	sum[1][i]=i;
	for(int i=2;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			if(ch[i-1]=='<')	f[i][j]=sum[i-1][j-1]%P;
			else	f[i][j]=(sum[i-1][i-1]-sum[i-1][j-1]+P)%P;
			sum[i][j]=(sum[i][j-1]+f[i][j])%P;
		}
	}
	printf("%d ",sum[n][n]);
	return 0;
}

B. F - Deforestation (AI)

n 个数:a1,a2,...,an

每次可以选择一个 i ,选择的代价是 ai1+ai+ai+1,然后令 ai=0 ,求有多少种方案,使得 a1,a2,...,an 都变为 0 的总代价最小,特别的 a0=an+1=0 1n40001ai1×109

考虑限制,对于 ii+1

  • 先拿 i 再拿 i+1 ,收益是 a[i]+2×a[i+1]
  • 先拿 i+1 再拿 i , 收益是 a[i+1]+2×a[i]

显然,对于更大的那个,先拿是最优的,如果相等就随意, ai 是原数组, posii 号是第几个取

于是类似于 T1

a[i1]>a[i] : f[i][j]=k=1j1f[i1][k]a[i1]<a[i] : f[i][j]=k=ji1f[i1][k]a[i1]=a[i] : f[i][j]=k=1i1f[i1][k]

#include"bits/stdc++.h"

using namespace std;
constexpr int N=5015,P=1e9+7;
#define inl inline
#define regi register
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
//#define ll long long
//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
//inl int add_(int a,int b,int p=P){return a+b>=p?a+b-p:a+b;}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}

inl int read(void)
{
	int x=0;short f=1;char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;
	for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
	return x*f;
}

int f[N][N],sum[N][N];
int n,a[N];

int main(void)
{
	n = read();
	for (int i = 1;i <= n;i ++)	a[i] = read();
	f[1][1] = 1;
	for (int i = 1;i <= n;i ++)	sum[1][i] = 1;
	for (int i = 2; i <= n;i ++)
	{
		for (int j = 1;j <= i;j ++)
		{
			if (a[i] < a[i - 1])	f[i][j] = sum[i - 1][j - 1];	else
			if (a[i] > a[i - 1])	f[i][j] = (sum[i - 1][i - 1] - sum[i - 1][j - 1] + P) % P;	else
			if (a[i] == a[i - 1])	f[i][j] = sum[i - 1][i - 1];
			sum[i][j] = (sum[i][j - 1] + f[i][j]) % P;
		}
	}
	printf ("%d ",sum[n][n]);
	return 0;
}

C. kangaroo

袋鼠,有 n 个草丛排成一排,编号从 1n ,有一个袋鼠,从 s 出发,每次挑一步到一个其他的草丛,经过每个草丛恰好一次,最终到达 t ,显然他会跳跃 n1 次,未来不被人类发现,袋鼠每次跳跃的方向必须与前一次不同
问从 st 的方案数 mod109+7的结果
两个路线不同,当且仅当草丛被访问的顺序不同,保证至少有一种方案初始时可以往任意方向跳
2n2×1031s,tn

f[i][j] 表示前 i 个数字,分成了 j 段,规定每段都是 一个元素 或者 低高低 或者 地高低高低 ...
对于处理 f[i][j] 时,第 i 个数字对于前 i1 个数字都是高的,所以他可以用来合并两个段,或者新开一段

f[i][j]=f[i1][j+1]×j+f[i1][j1]×j

考虑需要处理 s,t 的关系

i=si=t : f[i][j]=f[i1][j1]+f[i1][j]is&&it : f[i][j]=f[i1][j+1]×j+f[i1][j1]×(j(i>s)(i>t))

answer=f[n][1]

#include"bits/stdc++.h"

using namespace std;
constexpr int N=2e3+15,P=1e9+7;
#define inl inline
#define regi register
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
#define ll long long
//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
//inl int add_(int a,int b,int p=P){return a+b>=p?a+b-p:a+b;}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}

inl int read(void)
{
	int x=0;short f=1;char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;
	for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
	return x*f;
}

int n,s,t;
ll f[N][N];

int main(void)
{
	n = read(),s = read(),t = read();
	f[1][1] = 1;
	for(int i = 2;i <= n;i ++)
	{
		for(int j = 1;j <= i;j ++)
		{
			if(i != s && i != t)	f[i][j] = (j * f[i - 1][j + 1] % P + 
				(j - (i > s) - (i > t)) * f[i - 1][j - 1] % P) % P;
			else	f[i][j] = f[i - 1][j - 1] + f[i - 1][j];
		}
	}
	printf("%lld ",f[n][1]);
	return 0;
}

D.CF704B Ant Man

n 个元素,第 i 个元素有五个参数 xi,ai,bi,ci,di ,需要求出一个从 1n 的排列 p ,满足 p1=s,pn=e ,同时最小化这个排列的权值,定义一个排列的权值为 i=1n1f(pi.pi+1) , 其中 f(i,j) 的值有两种情况:

  • i>j , 则 f(i,j)=xixj+ci+bj
  • i<j , 则 f(i,j)=xjxi+di+aj

n5×103,se,1x1<x2<...<xn109,1ai,bi,ci,di109

f[i][j] 表示前 i 个数字,有 j 段的最小花费

f[0][1]=0

普通的情况
f[i][j]=f[i1][j1]x[i]×2+b[i]+d[i] 作为新开一段,这一段必然是在未来作为高低高出现的,所以他对答案的贡献是 2×x[i]+b[i]+d[i]

f[i][j]=f[i1][j+1]+x[i]×2+a[i]+c[i] 合并两个段,这个点最为低高低出现,对答案的贡献是2×x[i]+c[i]+a[i]

f[i][j]=f[i1][j]+b[i]+c[i] 往某个段前面放,作为高中低出现,对答案的贡献是 b[i]+c[i]

f[i][j]=f[i1][j]+a[i]+d[i] 往某个段后面放,作为低中高出现,对答案的贡献是 a[i]+d[i]

对于起点 sf[i][j]=f[i1][j]+x[i]+c[i] 连接在某个段前面,于是排列高低起手,s 的贡献是 x[i]+c[i]
新开一个段,f[i][j]=f[i1][j1]x[i]+d[i],于是最终排列低高起手, s 的贡献是 x[i]+d[i]

对于终点 tf[i][j]=f[i1][j]+x[i]+a[i] ,于是最终排列低高结尾,t 的贡献是x[i]+a[i]
新开一个段, f[i][j]=f[i1][j1]x[i]+b[i], 于是最终排列高低结尾, t 的贡献是 x[i]+b[i]

考虑起点和终点固定下来,对转移有什么影响:
想要新开一段时,当 i>s&&i>t 时,此时 s,t 做起点终点,只有一段时不能新开一段
想往某个段的前面放,要么 s 没出现,要么 s 出现了并且段数大于 1 才能往前放
想往某个段的后面放,要么 t 没出现,要么 t 出现了,并且段数大于 1 才能往后放

answer=f[n][1]

#include"bits/stdc++.h"

using namespace std;
constexpr int N=5e3+15;
#define inl inline
#define regi register
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
#define ll long long
//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
//inl int add_(int a,int b,int p=P){return a+b>=p?a+b-p:a+b;}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}

inl int read(void)
{
	int x=0;short f=1;char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;
	for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
	return x*f;
}

ll f[N][N];
int n,s,e;int x[N],a[N],b[N],c[N],d[N];

inl void cmin(ll &a,ll b){return a=min(a,b),void();} 

int main(void)
{
	n=read(),s=read(),e=read();
	for(int i=1;i<=n;i++)	x[i]=read();for(int i=1;i<=n;i++)	a[i]=read();for(int i=1;i<=n;i++)	b[i]=read();for(int i=1;i<=n;i++)	c[i]=read();for(int i=1;i<=n;i++)	d[i]=read();
	memset(f,0x3f,sizeof f);
	f[0][0]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			if(i==e)	cmin(f[i][j],min(f[i-1][j]+x[i]+a[i],f[i-1][j-1]-x[i]+b[i]));	else
			if(i==s)	cmin(f[i][j],min(f[i-1][j-1]-x[i]+d[i],f[i-1][j]+x[i]+c[i]));	else
			{
				if(j>(i>s)+(i>e))	cmin(f[i][j],f[i-1][j-1]-2*x[i]+b[i]+d[i]);
				if(j>1||i<s)	cmin(f[i][j],f[i-1][j]+b[i]+c[i]);
				if(j>1||i<e)	cmin(f[i][j],f[i-1][j]+a[i]+d[i]);
				cmin(f[i][j],f[i-1][j+1]+2*x[i]+a[i]+c[i]);
			}
		}
	}
	printf("%lld ",f[n][1]);
	return 0;
}

E. 「JOI Open 2016」摩天大楼

N 个整数, a1,a2,a3,...,aN,按照一定顺序排序,假设排列为 f1,f2,...,fN ,要求: f1f2+f2f3+...fN1fNL ,求满足题意的排列的方案数 (mod109+7)

为了规避掉绝对值,先排个序。

这题没有规定左右端点,考虑dp的时候加一维

f[i][j][sum][d]表示前 i 大的 a,有 j 段,当前已经固定了 d 个端点(左右端点),和为 sum

ai 插入的贡献看成是把之前的 j 段的每一段的左右端点都变为 ai ,需要花费 (2×jd)f[a]a[i1],然后插入 ai 时无需贡献,所以 aiai1的贡献是和插入前的段数和端点数量相关的,有 j 段,d 个端点时,贡献为 (aiai1)(2×jd)

考虑转移

 p  t  f[i][j][k][d]f[i+1][j+1][p][d]=f[i+1][j+1][p][d]+t(j+1d)j1f[i+1][j][p][d]=f[i+1][j][p][d]+t(2jd)j2f[i+1][j1][p][d]=f[i+1][j1][p][d]+t(j1)aid<2f[i+1][j+1][p][d+1]=f[i+1][j+1][p][d+1]+t(2d)aid<2&&j1f[i+1][j][p][d+1]=f[i+1][j][p][d+1]+t(2d)

#include"bits/stdc++.h"

using namespace std;
constexpr int N=115,L=1015,P=1e9+7;
#define inl inline
#define regi register
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
#define ll long long
//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
inl void add_(ll &a,ll b,int p=P){return a=(a+b>=p?a+b-p:a+b),void();}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}

inl int read(void)
{
	int x=0;short f=1;char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;
	for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
	return x*f;
}

ll f[N][N][L][3];int a[N],n,l;

int main(void)
{
	n=read(),l=read();
	for(int i=1;i<=n;i++)	a[i]=read();
	sort(a+1,a+1+n);
	f[0][0][0][0]=1;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<=i;j++)
		{
			for(int k=0;k<=l;k++)
			{
				for(short d=0;d<3;d++)
				{
					ll p=k+(a[i+1]-a[i])*(2*j-d),t=f[i][j][k][d];
					if(!t||p>l)	continue;add_(f[i+1][j+1][p][d],t*(j+1-d)%P);
					if(j>=2)	add_(f[i+1][j-1][p][d],t*(j-1)%P);
					if(j>=1)	add_(f[i+1][j][p][d],t*(2*j-d)%P);
					if(d<2)
					{
						add_(f[i+1][j+1][p][d+1],t*(2-d));
						if(j>=1)	add_(f[i+1][j][p][d+1],t*(2-d)%P);
					}
				}
			}
		}
	}
	ll ans=0;
	for(int i=0;i<=l;i++)	add_(ans,f[n][1][i][2]);
	printf("%lld",n==1?1:ans);
	return 0;
}

P2612 [ZJOI2012] 波浪

随机生成一个排列

排列的价值定义为相邻两个数差的绝对值累加

问价值大于等于 M 的概率

L=a2a1+a3a2...

与上题类似,现在有 n 个空位,从小到大以此放入 1 ~ n 个数

设计 f[l][i][j][k] 为放了 i 个数,形成 j 个连通块,价值为 k 且有 l 个边界放了数的方案

转移类似上一题,注意得开 __float128,代码用了两种 , 小测试点快一些

#include"bits/stdc++.h"

using namespace std;
constexpr int N=5015;
#define inl inline
#define regi register
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
#define ld __float128
//#define ll long long
//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
//inl int add_(int a,int b,int p=P){return a+b>=p?a+b-p:a+b;}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}

inl int read(void)
{
	int x=0;short f=1;char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;
	for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
	return x*f;
}
int n,m,K;
template <class T>
void Print(T ans)
{
	int num[100];
	num[0]=0;
	ans=ans*10;
	for(int i=1;i<K;i++)
	{
		num[i]=(int)ans;
		ans=(ans-num[i])*10;
	}
	num[K]=(int)(ans+0.5);
	for(int i=K;i>=1;i--)	if(num[i]>=10)	num[i]-=10,num[i-1]++;
	printf("%d.",num[0]);
	for(int i=1;i<=K;i++)	printf("%d",num[i]);
	puts("");
}
int l;
ld ans;
template <class T>
void solve(T f[][115][3][N])
{
	f[0][0][0][0]=1;
	for(int i=0;i<n;i++)
	{
		int cur=i%2;
		for(int j=0;j<=i+1;j++)	for(int op=0;op<=2;op++)	for(int s=0;s<=(n*n)/2;s++)	f[!cur][j][op][s]=0;
		for(int j=(i!=0);j<=i;j++)	for(int op=0;op<=2;op++)	for(int s=0,ts=2*j-op;ts<=(n*n)/2;s++,ts++)
		{
			if(!f[cur][j][op][s])	continue;
			ld t=f[cur][j][op][s];
			f[!cur][j+1][op][ts]=f[!cur][j+1][op][ts]+t*(j+1-op);
			f[!cur][j][op][ts]+=t*(2*j-op);
			if(j)	f[!cur][j-1][op][ts]+=t*(j-1);
			if(op!=2)
				f[!cur][j+1][op+1][ts]+=t*(2-op),f[!cur][j][op+1][ts]+=t*(2-op);
		}
	}
	for(int i=l;i<=5000;i++)	ans=(ans+f[n%2][1][2][i]);
	for(int i=1;i<=n;i++)	ans=ans/i;
	Print(ans);
}
ld f1[2][115][3][N];
double f2[2][115][3][N];
int main(void)
{
	n=read(),l=read(),K=read();
	if(n==1)	Print(1),exit(0);
	if(n<=8)	solve(f2);else	solve(f1);
	return 0;
}

CF1515E Phoenix and Computers

给定 N 台电脑,起初每台电脑都是关闭的
现在你可以随意打开电脑,但如果第 i1、第 i+1 台电脑是开启的,则第 i 台电脑也会自动开启,而你无法手动开启它
问你有多少种打开电脑的方法,使得最后所有电脑都是开着的

f[i][j]表示有i个电脑,组成j个连续已开启的段

转移有 新建段,段扩增,段合并 共五种情况

#include"bits/stdc++.h"

using namespace std;
constexpr int N=415;
#define inl inline
#define regi register
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
#define ll long long

//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
//inl void add_(ll &a,ll b,int p=P){return a=(a+b>=p?a+b-p:a+b),void();}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}

inl int read(void)
{
	int x=0;short f=1;char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;
	for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
	return x*f;
}

int n,P;
ll f[N][N];

int main(void)
{
	n=read(),P=read(),f[0][0]=1;
	for(int i=1;i<=n;i++)	for(int j=1;j<=i;j++)
	{
		f[i][j]+=f[i-1][j]*2*j;
		if(i>=2)	f[i][j]+=f[i-2][j]*2*j,f[i][j]+=f[i-2][j+1]*2*j;
		if(i>=3)	f[i][j]+=f[i-3][j+1]*j;
		(f[i][j]+=f[i-1][j-1]*j)%=P;
	}
	printf("%lld",f[n][1]);
	return 0;
}

P7967 [COCI2021-2022#2] Magneti

给定 n 个磁铁和 l 个空位,其中相邻空位之间的距离为 1,每个空位可放置一个磁铁。所有 n 个磁铁都必须被放置。每个磁铁可以吸引距离小于 ri 的其它磁铁。

求所有磁铁互不吸引的方案总数对 109+7 取模的结果。

f[i][j][s],表示用了前i个,j段,段的长度之和为s 方案数

(s>=r[i])f[i][j][s]+=f[i1][j][sr[i]]j2(s>=1)f[i][j][s]+=f[i1][j1][s1]j(s>=2r[i]1)f[i][j][s]+=f[i1][j+1][s2r[i]+1]j

最后还需要组合数一下。对于f[n][1][i],需要给n个磁铁的前后中间共n+1个“盒子”放置li空格,可以为空

转化为n+1个盒子,li+n+1个空格,不可以为空,抓化成li+n个间隙,赛n个挡板

ans=f[n][1][i]C(n+li,n)

#include"bits/stdc++.h"

using namespace std;
constexpr int N=2e5+15,P=1e9+7;
#define inl inline
#define regi register int
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
#define ll long long
//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
//inl int add_(int a,int b,int p=P){return a+b>=p?a+b-p:a+b;}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}

inl int read(void)
{
	int x=0;short f=1;char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;
	for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
	return x*f;
}

ll fac[N],inv[N];
ll qpow(ll a,ll b){ll res=1;a%=P;while(b){if(b&1)res=res*a%P;a=a*a%P;b>>=1;}return res;}
void init(int n_)
{
	fac[0]=inv[0]=1;
	for(regi i=1;i<=n_;i++)	fac[i]=fac[i-1]*i%P;
	inv[n_]=qpow(fac[n_],P-2);
	for(regi i=n_-1;i>=1;i--)	inv[i]=inv[i+1]*(i+1)%P;
}
ll C(ll n,ll m){return fac[n]*inv[n-m]%P*inv[m]%P;}
ll n,l,r[N],ans;
ll f[65][65][10015];
int main(void)
{
//	freopen("magnet.in","r",stdin),freopen("magnet.out","w",stdout);
	n=read(),l=read();
	init(20000);
	for(int i=1;i<=n;i++)	r[i]=read();
	sort(r+1,r+1+n);
	f[0][0][0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			for(int s=0;s<=l;s++)
			{
				if(s>=r[i])	(f[i][j][s]+=f[i-1][j][s-r[i]]*j*2)%=P;
				if(s>=1)	(f[i][j][s]+=f[i-1][j-1][s-1]*j)%=P;
				if(s>=2*r[i]-1)	(f[i][j][s]+=f[i-1][j+1][s-2*r[i]+1]*j)%=P;
			}
		}
	}
	for(int i=0;i<=l;i++)	ans=(ans+(f[n][1][i]*C(l+n-i,n))%P)%P;
	printf("%lld",ans);
	return 0;
}

再附上我们强大的QY学长的[cf账号](zzuqy - Codeforces)

posted @   Ech0_7  阅读(14)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示