联考20200727 T1 小$\omega$数排列
分析:
遇到这种恶心绝对值加排列的问题,大概会往重新排序从小到大加入的方向去思考
先把值从小到大排序,一个一个加入
假设现在已经加入的值构成一些连续段,现在分类讨论:
加入一个数形成新的一段,那么它对答案的贡献系数为\(-2\)
在一段的左右加一个数,它对答案不会有贡献
加入一个数让两段连在一起,那么它对答案的贡献系数为\(2\)
进行DP,设\(f_{i,j,k}\)表示放了\(i\)个数形成\(j\)段,贡献为\(k\)的方案数
发现边界情况需要单独考虑,于是加一维\(t=0/1/2\)
\(f_{i,j,k,t}\)表示放了\(i\)个数形成\(j\)段,贡献为\(k\),边界填了\(0/1/2\)的方案数
五种情况讨论:
1、加一个数形成新的一段
2、在一段左右加一个数
3、将两个段连起来
4、边界填一个数形成新的一段
5、在一段左右加一个数并且该数在边界上
现在我们发现\(k\)的范围很让人不爽,可能会出现负数,规模难以估计
我们在加入某一个数后强行让\(k'=k+(2j-t)a_i\),后面这个是要让所有段连通至少需要的代价,并且\(k'\geq 0\)
如果\(k'\)这个至少得贡献都大于了\(L\)就没有必要继续DP了
相当于利用了放缩的思想
复杂度\(O(3n^2L)\)
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<map>
#include<string>
#define maxn 105
#define MOD 1000000007
using namespace std;
inline int getint()
{
int num=0,flag=1;char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=num*10+c-48,c=getchar();
return num*flag;
}
int n,L;
int a[maxn];
int f[maxn][maxn][10*maxn][3];
inline int upd(int x){return x<MOD?x:x-MOD;}
inline void getf(int i,int j,int k,int t,int num)
{
k=k+(2*j-t)*a[i];
if(k<=L)f[i][j][k][t]=upd(f[i][j][k][t]+num);
}
int main()
{
n=getint(),L=getint();
for(int i=1;i<=n;i++)a[i]=getint();
sort(a+1,a+n+1);
if(n==1){printf("1\n");return 0;}
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(int t=0;t<=2;t++)
{
int tmp=f[i][j][k][t];if(!tmp)continue;
int val=k-(2*j-t)*a[i];
getf(i+1,j+1,val-2*a[i+1],t,1ll*tmp*(j+1-t)%MOD);
if(j)getf(i+1,j,val,t,1ll*tmp*(2*j-t)%MOD);
if(j>=2)getf(i+1,j-1,val+2*a[i+1],t,1ll*tmp*(j-1)%MOD);
if(t<2)
{
getf(i+1,j+1,val-a[i+1],t+1,1ll*tmp*(2-t)%MOD);
if(j)getf(i+1,j,val+a[i+1],t+1,1ll*tmp*(2-t)%MOD);
}
}
int ans=0;
for(int i=0;i<=L;i++)ans=upd(ans+f[n][1][i][2]);
printf("%d\n",ans);
}