P3722 [AH2017/HNOI2017] 影魔

P3722 [AH2017/HNOI2017] 影魔

题目背景

影魔,奈文摩尔,据说有着一个诗人的灵魂。事实上,他吞噬的诗人灵魂早已成千上万。

千百年来,他收集了各式各样的灵魂,包括诗人、牧师、帝王、乞丐、奴隶、罪人,当然,还有英雄。

每一个灵魂,都有着自己的战斗力,而影魔,靠这些战斗力提升自己的攻击。

题目描述

奈文摩尔有 n 个灵魂,他们在影魔宽广的体内可以排成一排,从左至右标号 1n。第 i 个灵魂的战斗力为 ki,灵魂们以点对的形式为影魔提供攻击力。对于灵魂对 i,j (i<j) 来说,若不存在 ks (i<s<j) 大于 ki 或者 kj,则会为影魔提供 p1 的攻击力。另一种情况,令 cki+1,ki+2,,kj1 的最大值,若 c 满足:ki<c<kj,或者 kj<c<ki,则会为影魔提供 p2 的攻击力,当这样的 c 不存在时,自然不会提供这 p2 的攻击力;其他情况的点对,均不会为影魔提供攻击力。

影魔的挚友噬魂鬼在一天造访影魔体内时被这些灵魂吸引住了,他想知道,对于任意一段区间 [a,b],位于这些区间中的灵魂对会为影魔提供多少攻击力,即考虑所有满足 ai<jb 的灵魂对 i,j 提供的攻击力之和。

顺带一提,灵魂的战斗力组成一个 1n 的排列:k1,k1,,kn

提示

对于 100% 的数据,1n,m200000,1p1,p21000

Solution:

题意简述:给定一个排列 kn ,对于 i<cj 的三元组有以下两类贡献:

p1: ki,kj 有一个是区间最大值,另一个是次大值

p2: min(ki,kj)<kc<max(ki,kj)

我们考虑对一个点 pos 分别求出左右两边第一个比它大的 k 的下标 L,R 可以用单调栈在 O(n) 解决

然后我们考虑如何刻画这个三元组的贡献:

kL,kRki,ki+1 显然分别对区间 [L,R][i,i+1]p1 的贡献

L,R,pos 显然满足一个东西

i[R+1,i1]R>kpos>pi

i[i+1,R1]L>kpos>pi

所以:

i[i+1,R1] 会在 [L,i] 上产生 p2 的贡献

i[R+1,i1] 会在 [i,R] 上产生p2 的贡献

这可太线段树了

我们开一颗主席树,以每个区间的左端点作为 rt 然后线段树上的下标对应右端点的区间,然后我们的贡献就可以这样维护:

if(i<n)E[i].emplace_back(i+1,i+1,p1);
if((1<=l)&&(r<=n))E[l].emplace_back(r,r,p1);
if((1<=l)&&(i+1<=r-1))E[l].emplace_back(i+1,r-1,p2);
if((l+1<=i-1)&&(r<=n))E[r].emplace_back(l+1,i-1,p2);

然后每次查询就是

ans=T.query(T.rt[l-1],T.rt[r],1,n,l,r);

然后注意一个东西,由于我的主席树的下标是挂在询问上的,所以最好写标记永久化,不然不好写 pushdown

Code

#include<bits/stdc++.h>
#define int long long
const int N=2e5+5;
using namespace std;
//Segment_Tree
struct Segment_Tree{
int cnt;
int rt[N];
struct Tree{
int ls,rs,val,tag;
}t[N*64];
void insert(int &x,int y,int l,int r,int L,int R,int k)
{
t[x=++cnt]=t[y];
int len=(-max(L,l)+min(r,R)+1);
t[x].val+=len*k;
if(L<=l&&r<=R)
{
t[x].tag+=k;
return ;
}
int mid=l+r>>1;
if(L<=mid)insert(t[x].ls,t[y].ls,l,mid,L,R,k);
if(mid<R) insert(t[x].rs,t[y].rs,mid+1,r,L,R,k);
}
int query(int x,int y,int l,int r,int L,int R)
{
if(!y)return 0;
int len=(-max(L,l)+min(r,R)+1);
if(L<=l&&r<=R)
{
return (-t[x].val+t[y].val);
}
int mid=l+r>>1,res=0;
if(L<=mid)res+=query(t[x].ls,t[y].ls,l,mid,L,R);
if(mid<R) res+=query(t[x].rs,t[y].rs,mid+1,r,L,R);
res+=len*(-t[x].tag+t[y].tag);
return res;
}
}T;
int n,m,p1,p2;
int a[N],st[N],L[N],R[N];
vector<tuple<int,int,int> >E[N];
void work()
{
cin>>n>>m>>p1>>p2;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
while(st[0]&&a[st[st[0]]]<a[i]){R[st[st[0]]]=i;st[0]--;}
L[i]=st[st[0]];
st[++st[0]]=i;
}
while(st[0]){R[st[st[0]]]=n+1;st[0]--;}
for(int i=1;i<=n;i++)
{
int l=L[i],r=R[i];
if(i<n)E[i].emplace_back(i+1,i+1,p1);
if((1<=l)&&(r<=n))E[l].emplace_back(r,r,p1);
if((1<=l)&&(i+1<=r-1))E[l].emplace_back(i+1,r-1,p2);
if((l+1<=i-1)&&(r<=n))E[r].emplace_back(l+1,i-1,p2);
}
for(int i=1;i<=n;i++)
{
T.rt[i]=T.rt[i-1];
for(auto [l,r,w] : E[i])
{
T.insert(T.rt[i],T.rt[i],1,n,l,r,w);
}
}
for(int i=1,l,r;i<=m;i++)
{
scanf("%lld%lld",&l,&r);
int ans=T.query(T.rt[l-1],T.rt[r],1,n,l,r);
printf("%lld\n",ans);
}
}
#undef int
int main()
{
//freopen("P3722.in","r",stdin);freopen("P3722.out","w",stdout);
work();
return 0;
}
posted @   liuboom  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示