bzoj2138

题意:
话说Nan在海边等人,预计还要等上M分钟。为了打发时间,他玩起了石子。 Nan搬来了N堆石子,编号为1到N,每堆包含Ai颗石子。每1分钟,Nan会在编号在[Li,Ri]之间的石堆中挑出任意Ki颗扔向大海(好疼的玩法),如果[Li,Ri]剩下石子不够Ki颗,则取尽量地多。为了保留扔石子的新鲜感,Nan保证任意两个区间[Li,Ri]和[Lj,Rj] ,不会存在Li<=Lj & Rj<=Ri的情况,即任意两段区间不存在包含关系。可是,如果选择不当,可能无法扔出最多的石子,这时NN就会不高兴了。所以他希望制定一个计划,他告诉你他m分钟打算扔的区间[Li,Ri]以及Ki。现在他想你告诉他,在满足前i-1分钟都取到你回答的颗数的情况下,第i分钟最多能取多少个石子。
100% N<=40000 M<=N 1<=L[i]<=R[i]<=N A[i]<=500

#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#define N 41000
using namespace std;
struct node{int l,r,h,id;}a[N];
struct node1{int l,r,lc,rc,lz,a,b,lza,lzb;}lt[2*N];
int s[N],tl,n,m,h[N],pos[N];
bool cmp(node x,node y)
{
    if(x.l<y.l) return 1;
    return 0;
}
void down(int now)
{
    int lc=lt[now].lc,rc=lt[now].rc;
    if(lt[now].lza)
    {
        lt[now].a+=lt[now].lza;
        if(lc) lt[lc].lza+=lt[now].lza;
        if(rc) lt[rc].lza+=lt[now].lza;
        lt[now].lza=0;
    }
    if(lt[now].lzb)
    {
        lt[now].b+=lt[now].lzb;
        if(lc) lt[lc].lzb+=lt[now].lzb;
        if(rc) lt[rc].lzb+=lt[now].lzb;
        lt[now].lzb=0;
    }
}
void upd(int now)
{
    int lc=lt[now].lc,rc=lt[now].rc;
    if(lc) down(lc);
    if(rc) down(rc);
    lt[now].a=min(lt[lc].a,lt[rc].a);
    lt[now].b=max(lt[lc].b,lt[rc].b);
}
void bt(int l,int r)
{
    int now=++tl;
    lt[now].l=l;lt[now].r=r;
    if(l<r)
    {
        int mid=(l+r)/2;
        lt[now].lc=tl+1;bt(l,mid);
        lt[now].rc=tl+1;bt(mid+1,r);
        upd(now);
    }
    else lt[now].a=s[a[l].r],lt[now].b=s[a[l].l-1];
}
int finda(int now,int l,int r)
{
    int lc=lt[now].lc,rc=lt[now].rc,mid=(lt[now].l+lt[now].r)/2;
    down(now);
    if(lt[now].l==l && lt[now].r==r) return lt[now].a;
    if(mid>=r) return finda(lc,l,r);
    else if(l>mid) return finda(rc,l,r);
    else return min(finda(lc,l,mid),finda(rc,mid+1,r));
}
int findb(int now,int l,int r)
{
    int lc=lt[now].lc,rc=lt[now].rc,mid=(lt[now].l+lt[now].r)/2;
    down(now);
    if(lt[now].l==l && lt[now].r==r) return lt[now].b;
    if(mid>=r) return findb(lc,l,r);
    else if(l>mid) return findb(rc,l,r);
    else return max(findb(lc,l,mid),findb(rc,mid+1,r));
}
void changea(int now,int l,int r,int d)
{
    int lc=lt[now].lc,rc=lt[now].rc,mid=(lt[now].l+lt[now].r)/2;
    down(now);
    if(lt[now].l==l && lt[now].r==r) {lt[now].lza+=d;return;}
    if(mid>=r) changea(lc,l,r,d);
    else if(l>mid) changea(rc,l,r,d);
    else changea(lc,l,mid,d),changea(rc,mid+1,r,d);
    upd(now);
}
void changeb(int now,int l,int r,int d)
{
    if(l>r) return;
    int lc=lt[now].lc,rc=lt[now].rc,mid=(lt[now].l+lt[now].r)/2;
    down(now);
    if(lt[now].l==l && lt[now].r==r) {lt[now].lzb+=d;return;}
    if(mid>=r) changeb(lc,l,r,d);
    else if(l>mid) changeb(rc,l,r,d);
    else changeb(lc,l,mid,d),changeb(rc,mid+1,r,d);
    upd(now);
}
void init()
{
    scanf("%d",&n);
    int x,y,z,p;scanf("%d%d%d%d",&x,&y,&z,&p);
    for(int i=1;i<=n;i++)
    {
        s[i]=((i-x)*(i-x)+(i-y)*(i-y)+(i-z)*(i-z))%p;
        s[i]+=s[i-1];
    }
    scanf("%d",&m);
    scanf("%d%d%d%d%d%d",&a[1].h,&a[2].h,&x,&y,&z,&p);
    for(int i=3;i<=m;i++) a[i].h=(a[i-1].h*x+a[i-2].h*y+z)%p;
    for(int i=1;i<=m;i++) scanf("%d%d",&a[i].l,&a[i].r),a[i].id=i;
    sort(a+1,a+m+1,cmp);
    for(int i=1;i<=m;i++) pos[a[i].id]=i;
    bt(1,m);
}
void solve()
{
    for(int i=1;i<=m;i++)
    {
        int p=pos[i],A=finda(1,p,m),B=findb(1,1,p);
        int k=min(A-B,a[p].h);
        printf("%d\n",k);
        changea(1,p,m,-k);
        changeb(1,p+1,m,-k);
    }
}
int main()
{
    init();
    solve();
    return 0;
}

题解:
它让我想起了这道题[POI2009]Lyz。。
将线段按左端点排序后,再按时间处理。现在时刻处理的是从左到右第k条线段。
想知道在当前线段放入x石子后有没有完美匹配,考虑用hall定理判定。
hall定理是一个子集,这题中显然可以贪心的取连续的一段线段。
s[i]表示石子前缀和,h[i]表示从左到右前i条线段已经放了多少石子,l[i],r[i]表示从左到右第i条线段左右端点
那么有完美匹配的条件就是x<=min(s[r[j]]-s[l[i]-1]-(h[j]-h[i-1]))(i<=k,j>=k)
化简后变为min((s[r[j]]-h[j])-(s[l[i]-1]-h[i-1]))
设s[r[j]]-h[j]=aj,s[l[i]-1]-h[i-1]=bi,这是只与i,j相关的两个量
那就是min(aj-bi)
显然可以由min(aj)-max(bi)得到
区间min(a)和max(b)容易用线段树维护
得到当前时刻答案x后,h[k..m]会改变,去线段树上对a,b打打标记就好

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值