[JZOJ5073]【GDOI2017第三轮模拟day1】影魔

题目描述

有一个长度为n的排列a[],定义一个点对(i,j) (i < j)的贡献:

  1. max(a[i+1 j1])<min(a[i],a[j]) ,贡献p1
  2. sort(a[i],a[j],max(a[i+1 j1])) 后, max(a[i+1 j1]) 为第二项,贡献p2
    给m个询问,每次询问一个区间[l,r]的贡献和。
    30%:1<= n,m <= 500。
    另30%: p1=2*p2。
    100%:1 <= n,m <= 200000;1 <= p1,p2 <= 1000。

分析

我们可以先从第二档分析一下。
p1=2*p2提示我们拆开p1,把情况1转化为2,怎么弄呢?
先看看情况2吧。考虑i作为左端点(右端点相似),对于一个点对(i,j),如果这 max(a[i+1 j1]) 小于 a[i] ,而且而且 a[j] 小于 max(a[i+1 j1]) ,则贡献。那么如果把后面那个条件去掉,在考虑i做完两个端点后,某些点对(i,j)被算了两次,而被算重的点对,恰好就是能贡献p1的。
那么做法出来了:
对于i作为左端点。
1. 用单调栈弄出每一个点i向右跳的第一个比他大的点,记为next[i];
2. 离线挂询问从右到左扫,把点i为左端点的贡献记在右端点上,即线段树给[i+1,next[i]]打上+p2标记。
3. 对于左端点在i的询问(l,r),查询[l,r]的权值和。
4. i为右端点的话就翻转所有下标再做一次就好了。

那么满分怎么做呢?考虑到(i,next[i])构成了贡献p1的点对,而又被加了两次的p2,那么用刚刚的算法,每次做到i,就给贡献数组的next[i]那一位加上p1-2*p2,注意到同一点对p1不会加两次。

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
const int N=200010;
struct rec
{
    int l,r,id;
}que[N];
struct seg
{
    ll sum,tag;
}tr[N*4];
int n,m,p1,p2,a[N],k,l[N],r[N],next[N],sta[N],st,le,i,j;
ll prt[N];
bool cmp(rec a,rec b)
{
    return a.l>b.l;
}
void down(int x,int l,int r)
{
    int m=(l+r)/2;
    tr[x].sum+=tr[x].tag*((ll)r-l+1);
    if (l!=r)
    {
        tr[x*2].tag+=tr[x].tag;
        tr[x*2+1].tag+=tr[x].tag;
    }
    tr[x].tag=0;
}
void change(int x,int l,int r,int i,int j,int val)
{
    if (i>j) return;
    int m=(l+r)/2;
    down(x,l,r);
    if (l!=r)
    {
        down(x*2,l,m);
        down(x*2+1,m+1,r);
    }
    if (l==i&&r==j)
    {
        tr[x].tag+=val;
        down(x,l,r);
        return;
    }
    if (j<=m) change(x*2,l,m,i,j,val);else
    if (m<i) change(x*2+1,m+1,r,i,j,val);else
    {
        change(x*2,l,m,i,m,val);
        change(x*2+1,m+1,r,m+1,j,val);
    }
    tr[x].sum=tr[x*2].sum+tr[x*2+1].sum;
}
ll get(int x,int l,int r,int i,int j)
{
    if (i>j) return 0;
    int m=(l+r)/2;
    down(x,l,r);
    if (l!=r)
    {
        down(x*2,l,m);
        down(x*2+1,m+1,r);
    }
    if (l==i&&r==j) return tr[x].sum;
    if (j<=m) return get(x*2,l,m,i,j);else
    if (m<i) return get(x*2+1,m+1,r,i,j);else
        return get(x*2,l,m,i,m)+get(x*2+1,m+1,r,m+1,j);
    tr[x].sum=tr[x*2].sum+tr[x*2+1].sum;    
}
void solve()
{
    fo(i,1,m) que[i].l=l[i],que[i].r=r[i],que[i].id=i;
    sort(que+1,que+1+m,cmp);
    a[n+1]=1e9;
    sta[(st=1)]=n+1;
    fd(i,n,1)
    {
        while (st&&a[sta[st]]<a[i]) st--;
        next[i]=sta[st];
        sta[++st]=i;
    }
    le=1;
    fd(i,n,1)
    {
        change(1,1,n+1,i+1,next[i],+p2);
        change(1,1,n+1,next[i],next[i],p1-2*p2);
        while (le<=m&&que[le].l==i)
        {
            prt[que[le].id]+=get(1,1,n+1,i,que[le].r);
            le++;
        }
        if (le>m) break;
    }
}
int main()
{
    freopen("sf.in","r",stdin);
//  freopen("sf.out","w",stdout);
    scanf("%d %d %d %d",&n,&m,&p1,&p2);
    fo(i,1,n) scanf("%d",a+i);
    fo(i,1,m) scanf("%d %d",l+i,r+i);
    solve();
    fo(i,1,(n+1)*4) tr[i].sum=tr[i].tag=0;
    fo(i,1,n/2) swap(a[i],a[n-i+1]);
    fo(i,1,m) 
    {
        l[i]=n-l[i]+1,r[i]=n-r[i]+1;
        swap(l[i],r[i]);
    }
    solve();
    fo(i,1,m) printf("%lld\n",prt[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值