聯考20200727 T1 小$\omega$數排列
阿新 • • 發佈:2020-07-28
分析:
遇到這種噁心絕對值加排列的問題,大概會往重新排序從小到大加入的方向去思考
先把值從小到大排序,一個一個加入
假設現在已經加入的值構成一些連續段,現在分類討論:
加入一個數形成新的一段,那麼它對答案的貢獻係數為\(-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); }