对于这类题目的特征:
1.排列性质
2.\(100\le n \le 5\times10^3\)
3.答案和排列的上升下降的突变点有关
套路:
按照某个从小到大的顺序插入,有了这个顺序就能算贡献或者方案数
贡献往往提前计算,存在每个元素有新开一段、合并两端、连在某一段后面的不同方案
有的允许了\(A\),\(W\),\(V\),\(M\) 形状不同的最终状态可能在前面是同一状态,每个状态是为后来的状态做准备的
新开一个段和连在某个段的边上并非同一个状态,新开一个段,未来会在两端之间插入更大的数字来合并两个 段,连在一个段的边上,则未来就不会如此
关于端点:
1.如果固定了端点,往往是当 \(i\) 是左右端点时特判
2.如果没有固定左右端点,往往多加一维记录当前确定了几个端点(如果左右端点的转移/贡献相同),或者多加两维记录当前确定了左/右端点(如果左右端点的转移/贡献并不相同)
QY太强了,令我的总结旋转
按照结尾数字排名进行的插入类\(DP\)
A. 【Atcoder_DP】T-排列 (AI)
有一个长度为\(N\) 正整数排列,再给一个由 \(<\) 和 \(>\) 组成的长为 \(N - 1\) 的字符串
对于任意满足 \(1 \le i \le N - 1\)的字符 \(s_i\) ,如果 \(s_i\) 是 \(<\) 则 \(P_i < P_{i+1}\) 、 如果 \(s_i\) 是 \(>\) 则 \(P_i > P_{i+1}\) 。求满足这样性质的排列 \(P\) 的方案数
\(N\le 3000\)
考虑 \(DP\) ,设 \(f[i][j]\) 表示对于前 \(i\) 个数字,最后一个数字在前 \(i\) 个数字里排名第 \(j\) 的方案数
显然可以列出递推式
用前缀和优化,就可以把时间复杂度优化到\(O(n^2)\)
#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\) 个数:\(a_1,a_2,...,a_n\)
每次可以选择一个 \(i\) ,选择的代价是 \(a_{i-1}+a_i+a_{i+1}\),然后令 \(a_i=0\) ,求有多少种方案,使得 \(a_1,a_2,...,a_n\) 都变为 \(0\) 的总代价最小,特别的 \(a_0=a_{n+1}=0\) \(1\le n \le 4000\),\(1 \le a_i \le 1\times 10^9\)
考虑限制,对于 \(i\) 和 \(i+1\)
- 先拿 \(i\) 再拿 \(i+1\) ,收益是 \(a[i]+2 \times a[i+1]\)
- 先拿 \(i+1\) 再拿 \(i\) , 收益是 \(a[i+1]+2 \times a[i]\)
显然,对于更大的那个,先拿是最优的,如果相等就随意, \(a_i\) 是原数组, \(pos_i\) 是 \(i\) 号是第几个取
于是类似于 \(T1\)
#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]; elseif (a[i] > a[i - 1]) f[i][j] = (sum[i - 1][i - 1] - sum[i - 1][j - 1] + P) % P; elseif (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\) 个草丛排成一排,编号从 \(1\) 到 \(n\) ,有一个袋鼠,从 \(s\) 出发,每次挑一步到一个其他的草丛,经过每个草丛恰好一次,最终到达 \(t\) ,显然他会跳跃 \(n-1\) 次,未来不被人类发现,袋鼠每次跳跃的方向必须与前一次不同
问从 \(s\) 到 \(t\) 的方案数 \(\mod 10^9+7\)的结果
两个路线不同,当且仅当草丛被访问的顺序不同,保证至少有一种方案初始时可以往任意方向跳
\(2\le n \le 2\times 10^3\),\(1 \le s,t \le n\)
用 \(f[i][j]\) 表示前 \(i\) 个数字,分成了 \(j\) 段,规定每段都是 一个元素 或者 低高低 或者 地高低高低 \(...\)
对于处理 \(f[i][j]\) 时,第 \(i\) 个数字对于前 \(i-1\) 个数字都是高的,所以他可以用来合并两个段,或者新开一段
\(f[i][j]=f[i-1][j+1]\times j+f[i-1][j-1]\times j\)
考虑需要处理 \(s,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\) 个元素有五个参数 \(x_i,a_i,b_i,c_i,d_i\) ,需要求出一个从 \(1\) 到 \(n\) 的排列 \(p\) ,满足 \(p_1=s,p_n=e\) ,同时最小化这个排列的权值,定义一个排列的权值为 \(\sum_{i=1}^{n-1}{f(p_i.p_{i+1})}\) , 其中 \(f(i,j)\) 的值有两种情况:
- 若 \(i>j\) , 则 \(f(i,j) = x_i - x_j+c_i+b_j\)
- 若 \(i<j\) , 则 \(f(i,j) = x_j - x_i + d_i + a_j\)
\(n\le 5\times 10^3,s≠e,1\le x_1 < x_2 < ... < x_n \le 10^9,1\le a_i,b_i,c_i,d_i \le 10^9\)
用 \(f[i][j]\) 表示前 \(i\) 个数字,有 \(j\) 段的最小花费
\(f[0][1]=0\)
普通的情况
\(f[i][j]=f[i-1][j-1]-x[i]\times 2+b[i]+d[i]\) 作为新开一段,这一段必然是在未来作为高低高的低出现的,所以他对答案的贡献是 \(-2\times x[i]+b[i]+d[i]\)
\(f[i][j]=f[i-1][j+1]+x[i]\times 2+a[i]+c[i]\) 合并两个段,这个点最为低高低的高出现,对答案的贡献是\(2\times x[i]+c[i]+a[i]\)
\(f[i][j]=f[i-1][j]+b[i]+c[i]\) 往某个段前面放,作为高中低的中出现,对答案的贡献是 \(b[i]+c[i]\)
\(f[i][j]=f[i-1][j]+a[i]+d[i]\) 往某个段后面放,作为低中高的中出现,对答案的贡献是 \(a[i]+d[i]\)
对于起点 \(s\) , \(f[i][j]=f[i-1][j]+x[i]+c[i]\) 连接在某个段前面,于是排列高低起手,\(s\) 的贡献是 \(x[i]+c[i]\)
新开一个段,\(f[i][j]=f[i-1][j-1]-x[i]+d[i]\),于是最终排列低高起手, \(s\) 的贡献是 \(-x[i]+d[i]\)
对于终点 \(t\),\(f[i][j]=f[i-1][j]+x[i]+a[i]\) ,于是最终排列低高结尾,\(t\) 的贡献是\(x[i]+a[i]\)
新开一个段, \(f[i][j]=f[i-1][j-1]-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])); elseif(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\) 个整数, \(a_1,a_2,a_3,...,a_N\),按照一定顺序排序,假设排列为 \(f_1,f_2,...,f_N\) ,要求: \(\|f_1-f_2\|+\|f_2-f_3\|+...\|f_{N-1}-f_N\|\le L\) ,求满足题意的排列的方案数 \((\mod 10^9+7)\)
为了规避掉绝对值,先排个序。
这题没有规定左右端点,考虑\(dp\)的时候加一维
\(f[i][j][sum][d]\)表示前 \(i\) 大的 \(a\),有 \(j\) 段,当前已经固定了 \(d\) 个端点(左右端点),和为 \(sum\)
把 \(a_i\) 插入的贡献看成是把之前的 \(j\) 段的每一段的左右端点都变为 \(a_i\) ,需要花费 \((2 \times j-d)\cdot\|f[a]-a[i-1]\|\),然后插入 \(a_i\) 时无需贡献,所以 \(a_i-a_{i-1}\)的贡献是和插入前的段数和端点数量相关的,有 \(j\) 段,\(d\) 个端点时,贡献为 \((a_i-a_{i-1})\cdot(2\times j-d)\)
考虑转移
#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 = \| a_2-a_1 \|+\|a_3-a_2\|...\)
与上题类似,现在有 \(n\) 个空位,从小到大以此放入 $1 $ ~ $ n$ 个数
设计 \(f[l][i][j][k]\) 为放了 \(i\) 个数,形成 \(j\) 个连通块,价值为 \(k\) 且有 $l $ 个边界放了数的方案
转移类似上一题,注意得开 \(\_\_float 128\),代码用了两种 , 小测试点快一些
#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\) 台电脑,起初每台电脑都是关闭的
现在你可以随意打开电脑,但如果第 \(i−1\)、第 \(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\) 个磁铁都必须被放置。每个磁铁可以吸引距离小于 \(r_i\) 的其它磁铁。
求所有磁铁互不吸引的方案总数对 \(10^9+7\) 取模的结果。
\(f[i][j][s]\),表示用了前\(i\)个,\(j\)段,段的长度之和为\(s\) 方案数
最后还需要组合数一下。对于\(f[n][1][i]\),需要给\(n\)个磁铁的前后中间共\(n+1\)个“盒子”放置\(l-i\)空格,可以为空
转化为\(n+1\)个盒子,\(l-i+n+1\)个空格,不可以为空,抓化成\(l-i+n\)个间隙,赛\(n\)个挡板
\(ans=\sum{f[n][1][i]}*C(n+l-i,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)