[USACO15JAN]Moovie Mooving G

[USACO15JAN]Moovie Mooving G


狀壓難題。不過也好理解。c++


首先咱們根據題意:測試

she does not want to ever visit the same movie twice.spa

這句話以及\(n\)的取值範圍給了咱們足夠多的提醒:多是狀態壓縮code

那麼,當咱們真正嘗試用集合表示每一個電影是否已看過的時候,咱們卻不容易給出可以轉移的狀態。get

舉個例子,當咱們定義的狀態中集合表示成爲該狀態的元素,那麼這將會與最終求該集合的最小值衝突——狀態中無法記錄集合的最小值。it

咱們不妨換一種思路:\(dp(i,S)\)表示的是以\(i\)爲結尾、已經看過的集合爲\(S\)最長時間。這種狀態在轉移的時候可以避免計算到不合法的狀態,咱們接下來具體分析:class

容易獲得:統計

\[dp(i,S)=max(dp(j,S\setminus\{i\}),query(dp(j,S\setminus\{i\}))+d_i) \]

其中,\(query(dp(j,S\setminus\{i\}))\)表明的是電影\(i\)中時間節點不超過\(dp(j,S\setminus\{i\})\)的最大值。而該狀態下容許不存在這樣的時間節點,轉移的時候特殊處理便可。總結

這是因爲計算的時候避免出現間隔——即看電影時間到處連續。集合

最後,統計最終答案的時候對於全部的大於等於限制\(L\)的狀態計算它們的集合大小,更新。

這樣作的時間複雜度爲\(O(n^22^nlogn)\)。實際測試的時候會更快。

…… 
int n, L, t, c[N], d[N], st[N][C], siz[S] = {}, ttt[S];
int ans = INF, dp[N][S];
int query(int cur, int x)
{
	int L = 0, R = c[cur], mid, tmp; 
	while(L < R)
	{
		mid = L + ((R - L) >> 1);
		tmp = st[cur][mid];		
		if(tmp == x) return x;
		if(tmp < x) L = mid + 1;
		else R = mid;
	}
	if(!R) return -1;
	return st[cur][R - 1];
}
int main()
{
	scanf("%d %d", &n, &L);
	for(int i = 0; i < n; ++ i) 
	{
		scanf("%d %d", &d[i], &c[i]);
		for(int j = 0; j < c[i]; ++ j) scanf("%d", &st[i][j]);
	}
	t = (1 << n) - 1;
	for(int i = 1; i <= t; ++ i) siz[i] = siz[i ^ (i & (-i))] + 1;
	for(int i = 0; i < n; ++ i) ttt[1 << i] = i;
	for(int i = 1; i <= t; ++ i) ttt[i] = ttt[i & (-i)];
	memset(dp, 0, sizeof(dp));
	for(int s = 1; s <= t; ++ s)
	{
		for(int s0 = s, i = ttt[s]; s0; s0 &= ~(1 << i), i = ttt[s0])
		{
			if(siz[s] == 1 && st[i][0] == 0) dp[i][s] = d[i];
			else
			{
				for(int s1 = s ^ (1 << i), j = ttt[s1]; s1; s1 &= ~(1 << j), j = ttt[s1])
				{
					int tmp = query(i, dp[j][s ^ (1 << i)]);
					dp[i][s] = max(dp[i][s], dp[j][s ^ (1 << i)]);
					if(tmp != -1) dp[i][s] = max(dp[i][s], tmp + d[i]);
				}
			}
//			printf("dp[%d][%d] = %d\n", i, s, dp[i][s]);
			if(dp[i][s] >= L) ans = min(ans, siz[s]);
		} 
	}
	if(ans == INF) puts("-1");
	else printf("%d\n", ans);
	return 0;
}

其實,咱們發現,在咱們轉移的時候,咱們徹底能夠僅記錄一個集合表明該集合下所能達到的最長時間結點。這樣一來,咱們只須要枚舉集合中的每個元素,直接轉移便可。

這是由於第一維狀態是無用的,由於轉移只和DP出的值有關,與元素無關。

時間複雜度:\(O(n2^nlogn)\)。空間也能夠承受的了。

……
int n, L, t, c[N], d[N], st[N][C], siz[S] = {}, ttt[S];
int ans = INF, dp[S];
int query(int cur, int x)
{
	int L = 0, R = c[cur], mid, tmp; 
	while(L < R)
	{
		mid = L + ((R - L) >> 1);
		tmp = st[cur][mid];		
		if(tmp == x) return x;
		if(tmp < x) L = mid + 1;
		else R = mid;
	}
	if(!R) return -1;
	return st[cur][R - 1];
}
int main()
{
	scanf("%d %d", &n, &L);
	for(int i = 0; i < n; ++ i) 
	{
		scanf("%d %d", &d[i], &c[i]);
		for(int j = 0; j < c[i]; ++ j) scanf("%d", &st[i][j]);
	}
	t = (1 << n) - 1;
	for(int i = 1; i <= t; ++ i) siz[i] = siz[i ^ (i & (-i))] + 1;
	for(int i = 0; i < n; ++ i) ttt[1 << i] = i;
	for(int i = 1; i <= t; ++ i) ttt[i] = ttt[i & (-i)];
	memset(dp, 0, sizeof(dp));
	for(int s = 1; s <= t; ++ s)
	{
		for(int s0 = s, i = ttt[s]; s0; s0 &= ~(1 << i), i = ttt[s0])
		{
			int tmp = query(i, dp[s ^ (1 << i)]);
			dp[s] = max(dp[s], dp[s ^ (1 << i)]);
			if(tmp != -1) dp[s] = max(dp[s], tmp + d[i]);
			if(dp[s] >= L) ans = min(ans, siz[s]);
		} 
	}
	if(ans == INF) puts("-1");
	else printf("%d\n", ans);
	return 0;
}

總結:

  1. 這道題勇於在轉移的過程當中進行非\(O(1)\)作法的計算;
  2. 當轉移與狀態中集合元素自己無關聯,與狀態值有關時應去除冗雜信息。
相關文章
相關標籤/搜索