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;
}
posted @ 2021-11-02 09:51  QuantAsk  阅读(31)  评论(0编辑  收藏  举报