P3118 [USACO15JAN]电影移动Moovie Mooving
题解
题目给你 \(N\) 部电影,每部有持续时间,播放次数及播放时间,要求你在 \(1\) ~ \(L\) 的时间里不间断的看电影,同一部电影只能看一次。
首先我们看到 \(N\) 的数据范围, \(N\) 只有 \(20\) ,我们看到如此之小的数据便可往状压动规思考,我们令二进制中第 \(i\) 位为第 \(i\) 部电影是否被播放过,状态 \(f[i]\) 为以 \(i\) 为状态所能够持续的最长时间,而我们为使持续时间最长,应使后接的那一部电影的开始时间尽量的靠近且早于前一步的结束时间,贪心一下,用二分即可,结果就是最优解中 \(1\) 的个数。
然后我们枚举 $\underbrace{0,\cdot\cdot\cdot,0}_n 到 \underbrace{1,\cdot\cdot\cdot,1}_n $ 就可以了。
统计二进制中一的个数可以用我们做树状数组的 \(lowbit\) 来计算, \(stl\) 有个函数可以计算但是是下划线开头的函数,考试时用不了。
转移为 \(f[i|(1<<(j-1))]=max(f[i|(1<<(j-1))],val)\), \(val\) 为在原有的状态 \(i\) 上接第 \(j\) 部电影可以持续的最长时间。
当然如果你纯粹这么一步步的跑下去是肯定会超时,所以,我们还需要几个特判来优化时间。
-
将所有的状态初始化为 \(-1\),\(f[0]=0\),这样遍历是若遇到 \(f[i]=-1\) 时可以直接 \(continue\)。
-
遇到第一个大于 \(L\) 的 \(f[i]\) 时用 \(ans\) 记录他的一的个数,之后凡是遍历到的 \(i\) 若其 \(1\) 的个数大于了记录的个数,直接 \(continue\)。
最后判断\(ans\)是否被赋值过,未被赋值输出\(-1\),否则输出结果。
代码如下
#include<bits/stdc++.h>
using namespace std;
#define in inline
#define ll long long
in int read()
{
int w=0,r=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')ch=getchar();
ch=getchar();
}
while(isdigit(ch))
{
w=(w<<1)+(w<<3)+(ch^48);
ch=getchar();
}
return w*r;
}
int n,l;
struct movie
{
int d,c,t[1010];
}m[30];
int f[1<<21];
bool vis[1<<21];
queue<int> q;
in int lowbit(int x){return x&(-x);}
in int getnum(int x)
{
int cnt=0;
while(x)
{
x-=lowbit(x);
cnt++;
}
return cnt;
}
in int max_(int x,int y)
{
return x>y?x:y;
}
int bs(int typ,int x)
{
int l=1,r=m[typ].c;
while(l<=r)
{
int mid=(l+r)>>1;
if(m[typ].t[mid]>x)r=mid-1;
else l=mid+1;
}
if(r<1)return -1;
else return m[typ].t[r]+m[typ].d-1;
}
int ans=0x3f3f3f3f;
int main()
{
// freopen("movie3.in","r",stdin);
// freopen("movie1.out","w",stdout);
n=read();
l=read();
for(int i=1;i<=n;i++)
{
int d=read(),c=read();
m[i].d=d;
m[i].c=c;
for(int j=1;j<=c;j++)
{
m[i].t[j]=read();
}
}
memset(f,-1,sizeof(f));
f[0]=0;
for(int i=0;i<=(1<<n)-1;i++)
{
int num=getnum(i);
if(num>=ans)continue;
if(f[i]>=l)
{
ans=num;
continue;
}
if(f[i]==-1)continue;
for(int j=1;j<=n;j++)
{
if((i>>(j-1))&1)continue;
int val=bs(j,f[i]+1);
if(val==-1)continue;
f[i|(1<<(j-1))]=max_(f[i|(1<<(j-1))],val);
}
}
if(ans>=0x3f3f3f3f)
{
puts("-1");
return 0;
}
cout<<ans<<endl;
return 0;
}
状压中对二进制各位的各种操作要记牢,二分什么的最好还是要会手写,毕竟要是碰到个什么特殊一点的,\(lower\)_\(bound\)做不了的就很难受了。