状压难题。不过也好理解。c++
首先咱们根据题意:测试
she does not want to ever visit the same movie twice.spa
这句话以及\(n\)的取值范围给了咱们足够多的提醒:多是状态压缩。code
那么,当咱们真正尝试用集合表示每一个电影是否已看过的时候,咱们却不容易给出可以转移的状态。get
举个例子,当咱们定义的状态中集合表示成为该状态的元素,那么这将会与最终求该集合的最小值冲突——状态中无法记录集合的最小值。it
咱们不妨换一种思路:\(dp(i,S)\)表示的是以\(i\)为结尾、已经看过的集合为\(S\)的最长时间。这种状态在转移的时候可以避免计算到不合法的状态,咱们接下来具体分析:class
容易获得:统计
其中,\(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; }
总结: