您的位置:首页 > 其它

BZOJ1135: [POI2009]Lyz

2017-03-01 13:50 267 查看
题目大意:初始时滑冰俱乐部有1到n号的溜冰鞋各k双。已知x号脚的人可以穿x到x+d的溜冰鞋。 有m次操作,每次包含两个数ri,xi代表来了xi个ri号脚的人。xi为负,则代表走了这么多人。 对于每次操作,输出溜冰鞋是否足够。

首先——

每次二分图匹配显然是爆炸的

所以有这么一个东西——

Hall定理:对于一个二分图,设左边有n个点,右边有m个点,则左边n个点能完全匹配的充要条件是:对于1<=i<=n,左面任意i个点,都至少有i个右面的点与它相连

然后——

我们肯定不能一一枚举所有的情况,这样是2人数的

我们考虑只挑一些有用的情况来算(也就是最差情况),我们可以把所有的人想象成一个1~n的序列,每个位置存人数。假设我们选的不是脚的型号在连续的一段区间的人的话,分两种情况讨论:

1.这些人对应的鞋的可选型号范围是连续的。那么我们不妨再选上中间没有选到的人,这样人数变多了,而范围不会扩大,这样选到的情况一定更劣的。所以我们无需计算最开始选择的那种方案

2.不是连续的。那就更加好办了,我们只需要计算每段连续的部分就行了。这种情况合法当且仅当每段连续的区间都合法。

于是——

综上所述我们只需要判定每个连续的人形成的段是否满足Hall定理即可

设我们选择的是脚在[l,r]区间内的人,a[i]代表型号为i的有多少人。

想满足Hall定理,则对于任意的l,r我们必须满足这个式子:

∑i=lra[i]<=(r−l+1+d)∗k

操作和询问是O(m)的,大约是50W,每次枚举端点+查询+修改是O(n2logn)的,时间复杂度还是接受不了

于是我们把式子换一种形式写出来

∑i=lr(a[i]−k)<=d∗k

d∗k是一个定值,现在相当于询问把每个a[i]−k之后查询最大连续和并检验其是否小于等于d∗k

那么这件事情就可以用线段树来维护了,需要支持单点修改,最开始的时候把所有的a[i]都设为−k就好了

#include<iostream>
#include<cstdio>
#define N 200010
using namespace std;
long long l[N<<2],r[N<<2],lm[N<<2],rm[N<<2],w[N<<2],sum[N<<2];
long long n,m,k,d;
void pup(long long x)
{
lm[x]=max(lm[x<<1],sum[x<<1]+lm[x<<1|1]);
rm[x]=max(rm[x<<1|1],sum[x<<1|1]+rm[x<<1]);
sum[x]=sum[x<<1]+sum[x<<1|1];
w[x]=max(w[x<<1],max(w[x<<1|1],rm[x<<1]+lm[x<<1|1]));
}
void build(long long now,long long ll,long long rr)
{
l[now]=ll;r[now]=rr;
if(ll==rr) {sum[now]=w[now]=-k;return;}
long long mid=(ll+rr)>>1;
build(now<<1,ll,mid);
build(now<<1|1,mid+1,rr);
pup(now);
}
void change(long long now,long long x,long long v)
{
if(l[now]==r[now])
{
w[now]+=v;sum[now]=w[now];
if(w[now]>0) lm[now]=rm[now]=w[now];
else lm[now]=rm[now]=0;
return;
}
long long mid=(l[now]+r[now])>>1;
if(x<=mid) change(now<<1,x,v);
else change(now<<1|1,x,v);
pup(now);
}
int main()
{
scanf("%lld%lld%lld%lld",&n,&m,&k,&d);
build(1,1,n);
long long i,j,x,y;
for(i=1;i<=m;i++)
{
scanf("%lld%lld",&x,&y);
change(1,x,y);
if(w[1]>k*d) puts("NIE");
else puts("TAK");
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: