P8175 [CEOI2021] Tortoise
P8175 [CEOI2021] Tortoise
定义一段区域表示一段极大的子区间,其中至少有一个糖果,并且没有垃圾站。
我们可以发现这样一个事实,对于一段区域,一定会先买一些糖,然后丢到这段区域左边的空地上,再买一些糖,丢到这段区域右边的空地上。后半段很好理解,假设在第
根据扔的方式不同,我们把所有行动分为三种。第一种:从某个糖出发,把它扔到左边离它最近的空地然后回来。第二种:从某个空地出发,往左走去买一颗糖,然后回来扔掉。第三种:从一个空地出发,买了一颗糖,走到下一个空地扔掉。那么我们会发现,对于每一段区域,我们都会先进行第一种行动,然后进行第三种行动,最后进行第二种行动。
问题在于我们不知道在哪个地方进行哪种行动,进行几次最优。那么我们可以考虑反悔贪心,先尽量扔,扔不了了再调剂。具体的,我们用大根堆维护扔掉每一颗糖需要付出的路程代价,对于每一个糖果商店的
还有一些具体的实现就在代码注释里讲吧。
code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int n;
int a[N],pre[N],nxt[N],to[N];
priority_queue<int> q;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
for (int i=1;i<=n;++i) cin>>a[i];
pre[0]=-1e9;
for (int i=1;i<=n;++i)//找到前一个空地
{
pre[i]=pre[i-1];
if (a[i]==-1) pre[i]=i;
}
nxt[n+1]=1e9;
for (int i=n;i>=1;--i)//找到后一个空地
{
nxt[i]=nxt[i+1];
if (a[i]==-1) nxt[i]=i;
}
int last=0;
for (int i=n;i>=1;--i)//这个数组的含义不太好描述,结合后面理解一下
{
if (a[i]==0) continue;
if (a[i]==-1)
{
last=0;
continue;
}
to[i]=last,last=i;
}
int sum=0,ans=0;
for (int i=1;i<=n;++i)
{
if (a[i]==-1) continue;
if (a[i]==0) continue;
int cnt=a[i]-1,dis=min(i-pre[i],nxt[i]-i)*2;
while (cnt)
{
if (sum<=i-1)//不用乘2的原因是Tom走到i也走了i-1的路程,相当于他只剩下i-1的路程可以继续走
{
q.push(dis);
sum+=dis;
++ans,--cnt;
continue;
}
else if (dis<q.top())
{
sum-=q.top();
q.pop();
--cnt;
sum+=dis;
q.push(dis);
continue;
}
else break;
}
dis=(i-pre[i])<<1;
if (!to[i]) dis=0;//直到下一个空地之间都没有有糖的地方,那么就把这颗糖顺路带过去
else dis=min(dis,(nxt[i]-to[i])<<1);//减to[i]而不是减i是因为这里所有的糖已经考虑完了,不用再回来了
if (sum<=i-1)
{
q.push(dis);
sum+=dis;
++ans;
}
else if (dis<q.top())
{
sum-=q.top();
q.pop();
sum+=dis;
q.push(dis);
}
}
ans=-ans;
for (int i=1;i<=n;++i) if (a[i]!=-1) ans+=a[i];
cout<<ans<<endl;
return 0;
}