[BZOJ2138]stone

Description

给定长度为n的序列A,ai表示位置i有多少个石子。
有m次操作,第i次操作从区间[li,ri]中选择ki个石子丢弃,如果不足ki个则全部丢弃
保证区间互不相交
最大化丢弃石子的字典序
n,m<=40000

Solution

考虑如果我们已经知道了每次要拿几个,如何判断是否合法?
一个显然的思路是网络流判断是否满流,换句话说是拆点判断完美匹配
hall定理!但是hall定理要枚举子集怎么办?
有一个结论是,如果每个X部的点向Y部连的都是一个区间且区间没有交集
只需要对按区间右端点排序之后的X部的连续区间验证hall定理即可
这个很显然可以反证
我们将a中没有被覆盖的点删去,设A为a的前缀和
设bi表示排序后第i次操作拿的石头数,B为b的前缀和
那么我们相当于要求 ∀ i &lt; j , B j − B i − 1 &lt; = A r j − A l i − 1 \forall i&lt;j,B_j-B_{i-1}&lt;=A_{rj}-A_{li-1} i<j,BjBi1<=ArjAli1
C j = B j − A r j , D i = B i − 1 − A l i − 1 C_j=B_j-A_{rj},D_i=B_{i-1}-A_{li-1} Cj=BjArj,Di=Bi1Ali1,我们要求 ∀ i &lt; j , C j &lt; = D i \forall i&lt;j,C_j&lt;=D_i i<j,Cj<=Di
考虑按时间处理操作,假如我们要让bt+=x
那么对于i∈[t+1,q],j∈[t,q],Cj和Di都会+=x
考虑对限制的影响,显然是有j∈[t,q]和i∈[1,t]会有影响
也就是 C j + x &lt; = D i C_j+x&lt;=D_i Cj+x<=Di
x &lt; = D i − C j x&lt;=D_i-C_j x<=DiCj
显然D取最小值C取最大值就是x的上界
这个东西随便用一个线段树维护一下就好了

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

int read() {
	char ch;
	for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
	int x=ch-'0';
	for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x;
}

const int N=4e4+5,inf=0x7fffffff;

int n,m,a[N],c[N],cnt[N],id[N],rk[N],mx[N<<2],mn[N<<2],tmx[N<<2],tmn[N<<2];

int sqr(int x) {return x*x;}

void add(int x,int z,int opt) {
	if (opt==0) mx[x]+=z,tmx[x]+=z;
	if (opt==1) mn[x]+=z,tmn[x]+=z;
}

void down(int v) {
	if (tmx[v]) {
		add(v<<1,tmx[v],0);
		add(v<<1|1,tmx[v],0);
		tmx[v]=0;
	}
	if (tmn[v]) {
		add(v<<1,tmn[v],1);
		add(v<<1|1,tmn[v],1);
		tmn[v]=0;
	}
}

void update(int v) {
	mx[v]=max(mx[v<<1],mx[v<<1|1]);
	mn[v]=min(mn[v<<1],mn[v<<1|1]);
}

void modify(int v,int l,int r,int x,int y,int z,int opt) {
	if (x>y) return;
	if (x<=l&&r<=y) {add(v,z,opt);return;}
	int mid=l+r>>1;down(v);
	if (x<=mid) modify(v<<1,l,mid,x,y,z,opt);
	if (y>mid) modify(v<<1|1,mid+1,r,x,y,z,opt);
	update(v);
}

int query_min(int v,int l,int r,int x,int y) {
	if (x<=l&&r<=y) return mn[v];
	int mid=l+r>>1,tmp=inf;down(v);
	if (x<=mid) tmp=min(tmp,query_min(v<<1,l,mid,x,y));
	if (y>mid) tmp=min(tmp,query_min(v<<1|1,mid+1,r,x,y));
	return tmp;
}

int query_max(int v,int l,int r,int x,int y) {
	if (x<=l&&r<=y) return mx[v];
	int mid=l+r>>1,tmp=-inf;down(v);
	if (x<=mid) tmp=max(tmp,query_max(v<<1,l,mid,x,y));
	if (y>mid) tmp=max(tmp,query_max(v<<1|1,mid+1,r,x,y));
	return tmp;
}

struct Q{int l,r,k,id;}q[N];
bool cmp1(Q a,Q b) {return a.r<b.r;}
bool cmp2(Q a,Q b) {return a.id<b.id;}

void init() {
	n=read();
	int x=read(),y=read(),z=read(),p=read();
	fo(i,1,n) a[i]=(sqr(i-x)%p+sqr(i-y)%p+sqr(i-z)%p)%p;
	m=read();
	q[1].k=read();q[2].k=read();x=read(),y=read(),z=read(),p=read();
	fo(i,3,m) q[i].k=(x*q[i-1].k%p+y*q[i-2].k%p+z)%p;
	fo(i,1,m) q[i].l=read(),q[i].r=read(),q[i].id=i;
	sort(q+1,q+m+1,cmp1);
	fo(i,1,m) rk[q[i].id]=i;
	sort(q+1,q+m+1,cmp2);
}

void prepare() {
	fo(i,1,m) {
		cnt[q[i].l]++;
		cnt[q[i].r+1]--;
	}
	fo(i,1,n) cnt[i]+=cnt[i-1];
	int Id=0;
	fo(i,1,n) if (cnt[i]) id[i]=++Id,c[Id]=a[i];
	fo(i,1,m) q[i].l=id[q[i].l],q[i].r=id[q[i].r];
	n=Id;fo(i,1,n) a[i]=c[i];
	fo(i,1,n) a[i]+=a[i-1];
}

int main() {
	freopen("forget.in","r",stdin);
	freopen("forget.out","w",stdout);
	init();prepare();
	fo(i,1,m) {
		modify(1,1,m,rk[i],rk[i],-a[q[i].r],0);
		modify(1,1,m,rk[i],rk[i],-a[q[i].l-1],1);
	}
	fo(i,1,m) {
		int t=rk[i];
		int x=query_min(1,1,m,1,t)-query_max(1,1,m,t,m);
		x=min(x,q[i].k);
		printf("%d\n",x);
		modify(1,1,m,rk[i],m,x,0);
		modify(1,1,m,rk[i]+1,m,x,1);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值