1. 程式人生 > >JZOJ5436. 【NOIP2017提高A組集訓10.30】Group

JZOJ5436. 【NOIP2017提高A組集訓10.30】Group

題意:

資料範圍:

Analysis:

首先這個肯定先排序,這樣好確定最大最小數的差。對於一個組裡的差即為最左最右端點。
考慮DP,為表全狀態設 f i , j ,

k f_{i,j,k} 表示做到第 i i 個,還有 j j
組沒分好,當前所有組的和為 k k 。因為我們是不斷往組裡加右端點,所以第三維是遞增的。但是每一組的最大最小差並沒有解決,我們只有 a i a_i
的資訊,並不能推算出轉移後 k k 是怎樣。
但我們可以這樣考慮,若一個組沒有被分完,那麼其組最後的右端點肯定大於 a i a_i ,那就把 a i a_i 暫時當做右端點。即對於 k k 加上 j ( a i a i 1 ) j*(a_i-a_{i-1}) 就是轉移後的和。
另外一種理解方式就是左右端點相減,相當於中間所有距離之和。
那麼就差分一下,加上 j ( a i a i 1 ) j*(a_i-a_{i-1}) 。(差分是套路要記住,很多DP都可以利用差分來得到一些特殊的性質)
轉移只要分:是否開新的組,是否直接分出一個新的組,是否作為舊組結尾來轉移就好了

Code:

# include<cstdio>
# include<cstring>
# include<algorithm>
using namespace std;
const int N = 2e2 + 5;
const int M = 1e3 + 5;
const int mo = 1e9 + 7;
typedef long long ll;
int f[2][N][M],a[N];
int n,k;
inline int inc(int x,int y) { return x + y >= mo ? x + y - mo : x + y; }
int main()
{
	freopen("group.in","r",stdin);
	freopen("group.out","w",stdout);
	scanf("%d%d",&n,&k);
	for (int i = 1 ; i <= n ; ++i) scanf("%d",&a[i]);
	sort(a + 1,a + n + 1); int nx = 0; f[0][0][0] = 1;
	for (int i = 0 ; i < n ; ++i,nx ^= 1)
	{
		int d = a[i + 1] - a[i];
		memset(f[nx ^ 1],0,sizeof(f[nx ^ 1]));
		for (int j = 0 ; j <= i ; ++j)
			for (int l = 0 ; l <= k ; ++l)
			if (f[nx][j][l])
			{
				int v = l + d * j; if (v > k) break;
				f[nx ^ 1][j + 1][v] = inc(f[nx ^ 1][j + 1][v],f[nx][j][l]);
				f[nx ^ 1][j][v] = inc(f[nx ^ 1][j][v],f[nx][j][l]);
				if (j) f[nx ^ 1][j][v] = inc(f[nx ^ 1][j][v],(ll)f[nx][j][l] * j % mo);
				if (j) f[nx ^ 1][j - 1][v] = inc(f[nx ^ 1][j - 1][v],(ll)f[nx][j][l] * j % mo);
			}
	}
	int ans = 0;
	for (int i = 0 ; i <= k ; ++i) ans = inc(ans,f[nx][0][i]);	
	printf("%d\n",ans);
	return 0;
}