P3991 [BJOI2017] 喷式水战改

P3991 [BJOI2017] 喷式水战改

题目背景

拿到了飞机的驾照(?),这样补给就不愁了

XXXX年XX月XX日

拿到了喷气机(??)的驾照,这样就飞得更快了

XXXX年XX月XX日

拿到了攻击机(???)的驾照(不存在的)

XXXX年XX月XX日

用铅版做夹层的话,机身可是会变重的呢

XXXX年XX月XX日

幸酱的特制快递,精确投递到了目标地点


又是核平的一天。

天音正在给喷气机做保养,并充填燃料。

这种喷气机是某姬(?????)特别制作的,发动机拥有三种工作状态

1、通常型(Original):在高空平飞或隐蔽飞行时进行的低功耗高效率工作状态

2、后期型(Extended):为在俯冲时最大化能量利用率而特别改造过的工作状态

3、增强型(Enhanced):在俯冲攻击结束后为产生极限扭力抬高高度的工作状态

在一次攻击中,喷气机将会经历"通常-后期-增强-通常"的工作流程

不同工作状态中,燃料的利用效率是不同的

现在天音正在调整喷气机燃料装填序列

你需要做的就是求出燃料能产生的最大总能量

为什么是你?

和平还是核平,选一个吧

题目描述

初始燃料序列为空。每次操作会向序列中的 pi 位置添加 xi 单位的同种燃料,该燃料每一单位在三种工作状态下能产生的能量分别为 ai,bi,ci

添加的位置 pi 是指,在添加后,加入的第一个单位燃料前面有 pi 个单位的原燃料。

全部的 xi 单位燃料依次放置,然后原来在 pi 位置的燃料(如果有的话)依次向后排列。

对于一个确定的燃料序列,其能产生的最大总能量为:将序列依次分成"通常-后期-增强-通常"四段(每段可以为空),每一段在对应工作状态下产生的能量之和的最大值。

对于每次添加操作,你需要给出该次操作使得最大总能量增加了多少。

如果对于这种计算方式没有直观的感受,可以查看样例说明。

输入格式

第一行一个数 n,表示操作个数。

接下来 n 行,每行 5 个数 pi,ai,bi,ci,xi,空格分隔,表示向序列中的 pi 位置添加 xi 单位的同种燃料

这种燃料每单位在通常、后期、增强工作状态下产生的能量分别为 ai,bi,ci

输出格式

n 行,每行一个数,表示该次操作后能量序列所能产生的最大总能量增加了多少。

对于 100% 的数据,1n105, 1ai,bi,ci1041xi109

对于 100% 的数据,保证插入时序列中至少已有 pi 单位的燃料。

50% 数据有梯度。

Solution:

果然还是太久没写 dp 了,连这么简单的转移都忘了。(也有可能是看见平衡树标签太忘乎所以导致的) (。í _ ì。)

先说最大化能量:

我们将一个同颜色连续段看成一个点。

假设我们现在要合并两个区间和一个点 [l,pos),[pos,pos],(pos,r] 我们不难想到 [pos,pos] 这个点内的工作状态显然是同一种。

我们将四个阶段记录为 0,1,2,3 ,其对应权值分别为 a,b,c,a
我们记数组 f[i][j] 表示在当前节点的子树下,以状态 i 开始,状态 j 结束的最大能量。那么我们就可列出转移方程:

fx[i][k]=maxj=in[i,k]fls[i][j]+valx[j]lenx+frs[j][k]

其中 lenx 是节点 x 所代表的同颜色连续段的长度,valx[j] 是将节点 x 染成 j 这种颜色是单点的价值。 valx[j]a,b,c,a

所以我们只需要在pushup的时候花费 O(30) 的代价来转移就好了,还是可以接受的。

实现:

那么如何实现插入一个颜色连续段呢,那么当然是平衡树了,我们维护一颗平衡树,按照连续段数来分裂,查询节点总数就从 rt 开始二分一下,然后找到分裂节点之后判断一下是否要将当前同颜色连续段分为两段。

Code:

#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;i++)
const int N=5e5+5;
using namespace std;
inline int rd(){return rand()*rand()+17;}
int f[N][4][4];
int cnt,rt,n,m,ans;
struct Tree{
int ls,rs,siz,len,sum,val[4],pri;
}t[N];
void debug(int x)
{
printf("Node: %lld: %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld\n",x,t[x].ls,t[x].rs,t[x].siz,t[x].len,t[x].sum,t[x].val[0],t[x].val[1],t[x].val[2],t[x].val[3],t[x].pri);
}
void upd(int x)
{
memset(f[x],0,sizeof(f[x]));
For(i,0,3)For(j,i,3)For(k,j,3)
f[x][i][k]=max(f[x][i][k],f[t[x].ls][i][j]+t[x].val[j]*t[x].len+f[t[x].rs][j][k]);
t[x].sum=t[t[x].ls].sum+t[t[x].rs].sum+t[x].len;
t[x].siz=t[t[x].ls].siz+t[t[x].rs].siz+1;
}
int Node(int siz,int x,int a,int b,int c)
{
t[++cnt]={0,0,siz,x,x,{a,b,c,a},rd()};upd(cnt);return cnt;
}
void splite(int x,int &a,int &b,int k)
{
if(!x){a=b=0;return;}int tmp=t[t[x].ls].siz+1;
if(tmp<=k){a=x;splite(t[x].rs,t[a].rs,b,k-tmp);}
else {b=x;splite(t[b].ls,a,t[b].ls,k);}
upd(x);
}
int merge(int x,int y)
{
if(!x||!y)return x|y;
if(t[x].pri<=t[y].pri){t[x].rs=merge(t[x].rs,y);upd(x);return x;}
t[y].ls=merge(x,t[y].ls);upd(y);return y;
}
int find(int k)
{
int x=rt,res=0;
while(x)
{
if(k<=t[t[x].ls].sum)x=t[x].ls;
else if(k<=t[t[x].ls].sum+t[x].len)return res+t[t[x].ls].siz+1;
else k-=t[t[x].ls].sum+t[x].len,res+=t[t[x].ls].siz+1,x=t[x].rs;
}
return res;
}
void work()
{
cin>>n;
for(int i=1,p,x,a,b,c;i<=n;i++)
{
scanf("%lld%lld%lld%lld%lld",&p,&a,&b,&c,&x);
int pos=find(p),l,mid,r,len_l,len_r,u=Node(1,x,a,b,c);
splite(rt,l,r,pos);splite(l,l,mid,pos-1);
len_l=p-t[l].sum,len_r=t[l].sum+t[mid].sum-p;
if(t[l].sum+t[mid].len==p)rt=merge(merge(l,mid),merge(u,r));
else
{
a=t[mid].val[0],b=t[mid].val[1],c=t[mid].val[2];
int lp=Node(mid,len_l,a,b,c);int rp=Node(mid,len_r,a,b,c);
rt=merge(merge(l,merge(lp,u)),merge(rp,r));
}
printf("%lld\n",f[rt][0][3]-ans);ans=f[rt][0][3];
}
}
#undef int
int main()
{
srand(998244353);
//freopen("P3991.in","r",stdin);
//freopen("P3991.out","w",stdout);
work();
return 0;
}
posted @   liuboom  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示