AGC012 - E: Camel and Oases
题意简述
沙漠中有
骆驼有两个操作:
- 走到距离在V以内的一个绿洲。
- 飞到任意一个绿洲,但V减少一半。V=0时不能飞。
问骆驼依次从每个绿洲出发,能否一次性遍历所有绿洲。
分析
首先预处理出
每飞一次相当于下一层。题目转化成钦定第一条线段,然后从每一层选一条线段,问能否覆盖整个区间。
万万没想到,这道题居然是状压DP!!!
s 中的后起第i位为1表示从第i层选出了一条线段。
f1[s] 表示状态s时从1起向右最远能延伸到哪,f2[s] 表示状态s时从n起向左最远能延伸到哪。
f1[s]=max(f1[s],upFind(f1[s0]))
f2[s]=min(f2[s],lowFind(f2[s0]−1))
意义是从s0 加上一条线段能延伸到哪。这个转移方程和我的写法有关,具体看代码。
时间复杂度O(log2V⋅log2n⋅2log2V+1)=O(Vlog2nlog2V)
检查答案时,对于第一层的每一条线段,寻找是否存在
最大时间复杂度
但实际上,第一层的线段条数是不能超过
所以最大时间复杂度为
总时间复杂度最大为
实现
代码
//Camel and Oases
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long lint;
int const N=2e5+10;
int const S=1<<19;
int n,V;
lint d[N];
int logV,a[25][N];
int U,f1[S],f2[S];
int upFind(int a[],int x)
{
int L=1,R=a[0];
while(L<R-1)
{
int mid=(L+R)>>1;
if(a[mid]<=x) L=mid+1;
if(a[mid]>x) R=mid;
}
if(a[L]>x) return a[L];
else return a[R];
}
int lowFind(int a[],int x)
{
int L=1,R=a[0];
while(L<R-1)
{
int mid=(L+R)>>1;
if(a[mid]<x) L=mid;
if(a[mid]>=x) R=mid-1;
}
if(a[R]<x) return a[R]+1;
else return a[L]+1;
}
int main()
{
scanf("%d%d",&n,&V);
logV=0;
while((1<<logV)<=V) logV++;
logV++;
for(int i=1;i<=n;i++) scanf("%lld",&d[i]),d[i-1]=d[i]-d[i-1];
d[n]=0;
for(int i=1;i<=logV;i++)
{
a[i][0]=1;
for(int j=1;j<=n;j++)
{
a[i][a[i][0]]=j;
if(d[j]>(V>>(i-1))) a[i][0]++;
}
}
if(a[1][0]>logV)
{
for(int i=1;i<=n;i++) printf("Impossible\n");
return 0;
}
U=(1<<logV)-1;
for(int s=0;s<=U;s++) f1[s]=0,f2[s]=n+1;
for(int s=0;s<=U;s+=2)
for(int i=2;i<=logV;i++)
{
int s0=1<<(i-1);
if(s&s0) continue;
f1[s|s0]=max(f1[s|s0],upFind(a[i],f1[s]));
f2[s|s0]=min(f2[s|s0],lowFind(a[i],f2[s]-1));
}
for(int i=1;i<=a[1][0];i++)
{
bool f=false;
int fr=a[1][i-1]+1,to=a[1][i];
if(i==1) fr=1;
for(int s=0;s<=U&&!f;s+=2)
if(fr<=f1[s]+1 && f2[U-s-1]-1<=to) f=true;
if(f) for(int j=fr;j<=to;j++) printf("Possible\n");
else for(int j=fr;j<=to;j++) printf("Impossible\n");
}
return 0;
}
注意
- 因为最多飞
log2V+1 次,所以全集状态U=2log2V+1−1 ,体现在题目中为219 ,而不是218 。 a[i][0] 做他用,有些地方写的可能会麻烦一些。