AT2365-[AGC012E]Camel and Oases【状压dp】
正题
题目链接:https://www.luogu.com.cn/problem/AT2365
题目大意
一个数轴上有\(n\)个点,开始你有个水壶容量为\(V\),你每次有两个操作
- 走到一个距离与你不超过\(V\)的点
- 让\(V=\lfloor\frac V2\rfloor\),然后跳到任意一个点。
对于每个点求从这个点出发能否走到所有点。
\(1\leq n,V\leq 2\times 10^5,-10^9\leq x_i\leq 10^9\)
解题思路
显然操作二的次数不会超过\(\log V\),考虑从这里入手。考虑有没有一种方案能够把序列分成若干段,每一段相邻的差不超过给其匹配的一个\(\lfloor\frac{V}{2^x}\rfloor\)且\(V\)的段刚好在起点。
并且因为\(n\)很大我们可以考虑改成用数字而不是下标维护它这一维,设\(f_{S}\)表示用集合\(S\)中的\(V\)能够从右边走到的最左边的位置,同理\(g_S\)表示从左边开始走,然后枚举总间段。
注意到这样的复杂度是\(O(nV)\)的,不过如果一段的左右都已经分出了\(\log V\)个段,那么这个位置也是不合法的(因为)。
时间复杂度:\(O(V\log V)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2e5+10,M=19;
int n,m,V,w[M],x[N],l[M][N],r[M][N],f[1<<M],g[1<<M],ans[N];
int main()
{
scanf("%d%d",&n,&V);w[0]=V;
while(V){V>>=1;w[++m]=V;}
swap(w[0],w[m]);
for(int i=1;i<=n;i++)scanf("%d",&x[i]);
for(int j=0;j<=m;j++){
l[j][1]=1;r[j][n]=n;
for(int i=2;i<=n;i++)
l[j][i]=(x[i]-x[i-1]<=w[j])?l[j][i-1]:i;
for(int i=n-1;i>=1;i--)
r[j][i]=(x[i+1]-x[i]<=w[j])?r[j][i+1]:i;
}
int MS=(1<<m);
for(int s=0;s<MS;s++){
f[s]=n+1;
for(int i=0;i<m;i++){
if(!((s>>i)&1))continue;
f[s]=min(f[s],l[i][f[s^(1<<i)]-1]);
g[s]=max(g[s],r[i][g[s^(1<<i)]+1]);
}
}
int cnt=0;
for(int i=1;i<=n;i++){
bool flag=0;
for(int s=0;s<MS;s++){
int t=(MS-1)^s;
if(g[s]>=i-1&&f[t]<=r[m][i]+1)
{flag=1;break;}
}
for(int j=i;j<=r[m][i];j++)
ans[j]|=flag;
cnt++;i=r[m][i];
if(cnt>m)break;
}
cnt=0;
for(int i=n;i>=1;i--){
bool flag=0;
for(int s=0;s<MS;s++){
int t=(MS-1)^s;
if(f[s]<=i+1&&g[t]>=l[m][i]-1)
{flag=1;break;}
}
for(int j=i;j>=l[m][i];j--)
ans[j]|=flag;
cnt++;i=l[m][i];
if(cnt>m)break;
}
for(int i=1;i<=n;i++)
if(ans[i])puts("Possible");
else puts("Impossible");
return 0;
}