Luogu4501 [ZJOI2018]胖
Luogu4501 [ZJOI2018]胖
膜拜\(\operatorname{zhouchenyuan}\)秒杀这道题。
线段树
我们可以观察一下Bellman-Ford算法在这张图上是如何进行松弛的,对于一个拥有从\(0\)号点来的边的瞭望塔\(a_i\)来说,它在第一步一定会被边\(0-u\)松弛,随后向两边扩张,直到遇到阻碍后停止,形成一段\(a_i\)松弛过的区间,这个区间的边界之后可能被两边的瞭望塔重新松弛。
我们发现,如果我们能够对于每一个\(a_i\),找到对应的一段区间,那么我们就可以把每个瞭望塔的贡献全部计算出来,从而得到答案。
我们可以二分答案,然后判断是否合法,对于一个\(a_i\),我们通过二分求出其左右边界,先看如何处理左边界:
假设我们二分到的区间为\([g,a_i]\),我们需要保证两个条件:
\(1.\)\(a_i\)不能被区间\([g,a_i)\)中的瞭望塔拦截。
\(2.\)\(a_i\)到达\(g\)点的路程小于在它之前从\(g\)左侧瞭望塔到达\(g\)点的路程。
直接处理第一个条件并不容易,但是我们可以利用第一个条件计算出二分边界,就可以丢掉这个限制。
令\(s_k=\sum_{i=1}^{k-1} w_i\),我们可以利用\(s_i-s_j\)表示\(i,j\)之间的距离。
对于一个瞭望塔\(a_j\),如果\(j\)不会拦截\(i\),那么就满足:
我们用一棵线段树维护区间\(l_i+s_i\)的最小值(无瞭望塔处设为\(+\infty\)),然后利用倍增计算出二分边界。
然后我们就可以二分了,同时判断第二个条件是否成立。首先,要想抢夺\(a_i\)对\(g\)的控制,就必须比\(a_i\)先到达\(g\),那么对于\(g\)有影响的区间就是\([\max(1,g-(u-g)),g]\),对于瞭望塔\(a_j\),满足:
再利用一棵线段树维护区间\(l_i-s_i\)最小值即可。
对于右边界也是同理的。
还有一个关键点,就是如果存在一个点\(j\)满足\(l_i+dis(i,g)=l_j+dis(j,g)\),那么我们需要注意一下,如果\(j\)比\(i\)先到达\(g\),我们不必在\(i\)处计算答案,因为在\(j\)处一定会计算到。但是如果\(i,j\)同时到达\(g\),且没有其他点会先抢夺到\(g\),我们就会漏解,因此我们可以在枚举左边界时对这种情况进行特判。
时间复杂度:\(O(n \log^2 n)\)。
\(Code:\)
#include<iostream>
#include<cstdio>
#include<algorithm>
#define N 200005
#define ll long long
using namespace std;
const ll INF=1919191919191919;
int n,m,k,w[N],a[N],len[N];
ll ans,s[N],vl[N],gl[N];
struct sgt
{
ll tr[N << 2];
void build(int p,int l,int r)
{
tr[p]=INF;
if (l==r)
return;
int mid(l+r >> 1);
build(p << 1,l,mid);
build(p << 1 | 1,mid+1,r);
}
void modify(int p,int l,int r,int x,ll y)
{
if (l==r)
{
tr[p]=y;
return;
}
int mid(l+r >> 1);
if (x<=mid)
modify(p << 1,l,mid,x,y); else
modify(p << 1 | 1,mid+1,r,x,y);
tr[p]=min(tr[p << 1],tr[p << 1 | 1]);
}
void modify(int x,ll y)
{
modify(1,1,n,x,y);
}
ll calc(int p,int l,int r,int x,int y)
{
if (l==x && r==y)
return tr[p];
int mid(l+r >> 1);
if (y<=mid)
return calc(p << 1,l,mid,x,y); else
if (x>mid)
return calc(p << 1 | 1,mid+1,r,x,y); else
return min(calc(p << 1,l,mid,x,mid),calc(p << 1 | 1,mid+1,r,mid+1,y));
}
ll calc(int x,int y)
{
return calc(1,1,n,x,y);
}
}s1,s2;
int calcL(ll s,int I)
{
if (!I)
return 1;
for (int i=20;i>=0;--i)
if (I-(1 << i)+1>0 && s1.calc(I-(1 <<i)+1,I)>s)
I-=(1 << i);
if (!I || vl[I]<=s)
++I;
return I;
}
int calcR(ll s,int I)
{
if (I>n)
return n;
for (int i=20;i>=0;--i)
if (I+(1 << i)-1<=n && s2.calc(I,I+(1 << i)-1)>s)
I+=(1 << i);
if (I>n || gl[I]<=s)
--I;
return I;
}
ll calc1(int l,int r)
{
return s2.calc(l,r);
}
ll calc2(int l,int r)
{
return s1.calc(l,r);
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<n;++i)
scanf("%d",&w[i]),s[i+1]=s[i]+w[i];
s1.build(1,1,n),s2.build(1,1,n);
for (int i=1;i<=n;++i)
vl[i]=gl[i]=INF;
while (m--)
{
scanf("%d",&k);
for (int i=1;i<=k;++i)
{
scanf("%d%d",&a[i],&len[i]);
vl[a[i]]=s[a[i]]+len[i];
gl[a[i]]=-s[a[i]]+len[i];
s1.modify(a[i],vl[a[i]]);
s2.modify(a[i],gl[a[i]]);
}
ans=0;
for (int i=1;i<=k;++i)
{
int u(a[i]);
int lim(calcL(vl[u],u-1));
int l(lim),r(u),g(u);
while (l<=r)
{
int mid(l+r >> 1);
ll L(s[u]-s[mid]+len[i]-s[mid]);
int pt(max(1,mid-(u-mid)));
if (calc1(pt,mid)>L)
g=mid,r=mid-1; else
l=mid+1;
}
ans+=u-g+1;
if (g!=1)
{
--g;
ll L(s[u]-s[g]+len[i]-s[g]);
int pt(max(1,g-(u-g)));
if (g-(u-g)>0 && calc1(pt+1,g)>L && gl[pt]==L)
++ans;
}
}
for (int i=1;i<=k;++i)
{
int u(a[i]);
int lim(calcR(gl[u],u+1));
if (lim==u)
continue;
int l(u+1),r(lim),g(-1);
while (l<=r)
{
int mid(l+r >> 1);
ll L(-s[u]+s[mid]+len[i]+s[mid]);
int pt(min(n,mid+(mid-u)));
if (calc2(mid,pt)>L)
g=mid,l=mid+1; else
r=mid-1;
}
if (g==-1)
continue;
ans+=g-u;
}
printf("%lld\n",ans);
for (int i=1;i<=k;++i)
{
vl[a[i]]=gl[a[i]]=INF;
s1.modify(a[i],INF);
s2.modify(a[i],INF);
}
}
return 0;
}