题解:P10438 [JOISC 2024] 塔楼 (Day3)
P10438
现有的两篇题解都是两
情况一
第一个情况是
先编一个
优化以上 dp。能通过的位置是一段一段的,先猜一下 dp 值长什么样子。
对于每一段,肯定有一段前缀的 dp 值是
继续猜,每一段不是
首先因为
所以 dp 值的连续段有
情况二
第一个情况是
同样的,考虑 dp,令
同样的,dp 值不为
显然的,现在每一段不是全相等了,因为可以对
考虑这前
于是 dp 值又被刻画出来了,每一段都是不降的,且段与段之间也是不降的,每一段的 dp 值形态是长为
维护每段端点,开头的值,开头的长度,添加一段的时候通过二分算出开头的长度,复杂度
两部分结合起来算 dp,查询二分到对应的段落查询,复杂度
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,q,D,A,B,lft,rgt,mid,flg;
pair<int,int>a[200003];
int k1,k2,k3,k4,k5,k6,k7,k8,k9;
struct Segmt{
int l;
int r;
int stv;
int stlen;
}P[200003];
int totP;
int getval(int num,int pos){
if(!flg)return P[num].stv;
if(P[num].stlen==D)return P[num].stv+((pos-P[num].l)/D);
return P[num].stv+((pos-P[num].l+D-P[num].stlen)/D);
}
signed main(){
ios::sync_with_stdio(false);
cin>>n>>q>>D>>A>>B;
if(A*D>B)flg=1;
for(int i=1;i<=n;i++)cin>>a[i].first>>a[i].second;
P[++totP].l=0;P[totP].r=a[1].first-1;P[totP].stv=0;P[totP].stlen=D;
a[n+1].first=1000000000001ll;
a[n+1].second=1000000000001ll+D-1;
for(int i=1,j=0;i<=n;i++){
int u=a[i].second+1,v=a[i+1].first-1;
lft=1;rgt=totP;
while(lft<rgt){
mid=((lft+rgt)/2);
if(P[mid].r+D>=u)rgt=mid;
else lft=mid+1;
}
j=lft;
if(j>totP)continue;
if(max(P[j].l+D,u)>min(P[j].r+D,v))continue;
P[++totP].l=max(P[j].l+D,u);P[totP].r=v;P[totP].stv=getval(j,P[totP].l-D)+1;
if(!flg){P[totP].stlen=0;continue;}
k1=P[totP].stv-1;
lft=j;rgt=totP-1;
while(lft<rgt){
mid=((lft+rgt)/2);
if(getval(mid,P[mid].r)>k1||getval(mid,P[mid].l)>k1)rgt=mid;
else lft=mid+1;
}
if(lft>=totP||lft<j||getval(lft,P[lft].r)==k1){P[totP].stlen=D;continue;}
int pos=0;
if(getval(lft,P[lft].l)>k1)pos=P[lft].l;
else pos=P[lft].l+P[lft].stlen+D*(k1-P[lft].stv);
pos+=D;
if(pos>=P[totP].l&&pos<=P[totP].r)P[totP].stlen=pos-P[totP].l;
else P[totP].stlen=D;
if(P[totP].stlen==0)P[totP].stlen=D;
}
while(q--){
cin>>k1;
lft=1;
rgt=totP;
while(lft<rgt){
mid=((lft+rgt)/2)+1;
if(P[mid].l<=k1)lft=mid;
else rgt=mid-1;
}
if(P[lft].l<=k1&&P[lft].r>=k1)cout<<A*(k1-getval(lft,k1)*D)+B*getval(lft,k1)<<'\n';
else cout<<-1<<'\n';
}
return 0;
}