bzoj 4295 Hazard 【循环】【单调队列】【扫描】

第i个人抽到的序列与(i%m)是一样的,所以只需要讨论i%m剩余系下的问题。

首先搞出来每个人抽牌的循环,然后我们要判定这个人需要多少个循环加多少轮才能没钱。

需要知道两个东西,一个循环中最小的前缀和和一个循环的和。这样循环数就大约是(初始钱数-最小前缀和-1)/一个循环的和+1,还需要的轮数就是跑完循环之后第一次到0的轮数。

现在考虑维护方法:(我比较弱,这些想了好久)

先复制一次,循环的和显然,最小前缀和用单调队列。还需要的轮数我们从后往前扫整个序列,维护每个数最前面出现的位置,然后查询就可以了。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>

#define ll long long
#define inf 2000000000000000000ll
#define eps 1e-10
#define md
#define N 4000010
using namespace std;
const int D=2000000;
struct QQ { int dt,id;} q[N];
int a[N],b[N],r[N],sum[N],mn[N],T[N];
char st[N];
bool vis[N];
ll lun[N],ed[N],win[N],last[N];
int main()
{
#ifndef ONLINE_JUDGE
	freopen("data.in","r",stdin); freopen("data.out","w",stdout);
#endif
	int n,m;
	scanf("%d",&n);
	for (int i=0;i<n;i++) scanf("%d",&a[i]);
	scanf("%d",&m); scanf("%s",st);
	for (int i=0;i<m;i++) b[i]=st[i]=='W'?1:-1;
	for (int i=0;i<m;i++)
	  if (!vis[i])
	  {
	  	int x=i,tot=0;
	  	while (!vis[x])
	  	{
	  		vis[x]=1; r[++tot]=x; sum[tot]=sum[tot-1]+b[x];
	  		x=(x+n)%m;
	  	}
	  	for (int j=1;j<=tot;j++)
	  	{
	  		r[j+tot]=r[tot];
	  		sum[j+tot]=sum[j+tot-1]+b[r[j]];
	  	}
	  	int h=1,w=0;
	  	for (int j=1;j<=tot;j++)
	  	{
	  		while (h<=w&&q[w].dt>=sum[j]) w--;
	  		q[++w].id=j; q[w].dt=sum[j];
	  	}
	  	for (int j=1;j<=tot;j++)
	  	{
	  		while (h<=w&&q[h].id<j) h++;
	  		mn[j]=-(q[h].dt-sum[j-1]);
	  		win[j]=sum[j+tot-1]-sum[j-1];
	  		while (h<=w&&q[w].dt>=sum[j+tot]) w--;
	  		q[++w].id=j+tot; q[w].dt=sum[j+tot];
	  	}
	  	for (int j=D-(tot<<1);j<=D+(tot<<1);j++) last[j]=inf;
	  	for (int j=(tot<<1);j>tot;j--)
	  	  last[sum[j]+D]=j;
	  	for (int j=tot;j;j--)
	  	{
	  		x=r[j]; T[x]=tot;
	  		last[sum[j]+D]=j;
	  		while (x<n)
	  		{
	  			if (win[j]>=0&&a[x]>mn[j]) lun[x]=inf;
	  			else
	  			{
	  				if (a[x]<=mn[j]) lun[x]=0;
	  				else lun[x]=(a[x]-mn[j]-1)/(-win[j])+1;
	  				ed[x]=a[x]+lun[x]*win[j];
	  				ed[x]=last[-ed[x]+sum[j-1]+D]-(j-1);
	  			}
	  			x+=m;
	  		}
	  	}
	}
	ll ans=inf;
	for (int i=0;i<n;i++)
	{
	  if (lun[i]==inf) continue;
	  ans=min(ans,(lun[i]*T[i]+ed[i]-1)*n+i+1);
	}
	if (ans==inf) printf("-1\n");
	  else printf("%lld\n",ans);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值