P3118 [USACO15JAN] Moovie Mooving G

题目描述

部电影,每部时长为 共放映 场,且第 部电影第 场的开场时间为
每部电影只能看一次,看电影的中途可以换电影,求连续看满时长 最少要看几部电影。

思路

数据范围 ,考虑状压 DP。
表示在状态 时,可以连续看多久电影。
对于每个状态 ,暴力枚举接下来我们要看的电影。

因为要求看的电影数量尽可能少,所以每次应该选择最后一个不大于当前时间的电影场次。这个操作可以用 upper_bound 解决。

然后做正常的状压 DP 就可以了,设当前选到了电影 ,只有 存在不大于当前时间的场次,才进行转移。当前状态应当与电影结束时间取最大值,即

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
using namespace std;
template <typename T>
inline void read(T&x){//快读
int w=0;x=0;
char ch = getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') w = 1;
ch = getchar();
}
while(ch>='0' && ch<='9'){
x = (x<<1)+(x<<3)+(ch^48);
ch = getchar();
}
if(w) x=-x;
}
template <typename T,typename...Args>
inline void read(T&t,Args&...args){
read(t);read(args...);
}
const int N = 25,INF = 0x3f3f3f3f;
int n,L;
int val[N],num[N],tim[N][1100];
int ans=INF,dp[(1<<20)+10];
int main(){
read(n,L);
for(int i=1;i<=n;++i){
read(val[i],num[i]);//电影时长、场次数量
for(int j=1;j<=num[i];++j){
read(tim[i][j]);//每场的开始时间
}
}
for(int s=0;s<1<<n;++s){
for(int i=1;i<=n;++i){
if((1<<(i-1))&s) continue;//电影已经选过
int idx = upper_bound(tim[i]+1,tim[i]+num[i]+1,dp[s])-(tim[i]+1);
if(idx>0) dp[s|(1<<(i-1))] = max(dp[s|(1<<(i-1))],tim[i][idx]+val[i]); //存在场次
}
if(dp[s]>=L) ans = min(ans,__builtin_popcount(s));//popcount非常快
}
printf("%d",ans==INF? -1 : ans);
return 0;
}