[loj#2529] [ZJOI2018] 胖
题意简述
直线上有 (n) 个点,第 (i) 个点与第 (i+1) 个点之间连双向边,边权为 (w[i])
另有一点 (S)
(m) 次询问:给定 (k) 条连接 (S) 与直线上一点的双向边(有边权),在新图上以 (S) 为源点跑 (Bellman-Ford) ,求跑完后所有点被更新次数的总和。
(n,m,k leq 2 imes 10^5)
想法
更新次数看上去难搞,仔细想后发现,对直线上每一个点,将 (S) 分别通过 (k) 条路走到该点的路径的边权和 (s) 及 路径上经过的边数 (t) 做一个单调队列(随 (t) 增大,(s) 减小),则该点被更新的次数就是单调队列中的元素数目。
发现仍不太好做。转而考虑每条从 (S) 到直线的边可以更新多少点,发现是一个区间。
于是二分+st表判断求区间的两端。注意计数时不要记重了……
复杂度 (O(nlog^2n))
总结
思想
可以换对象考虑问题。
要有影响区间的意识,二分意识。
码力
(set) 中似乎不一定 --s.begin()==s.end()
,所以用 (upper\_ bound) 时如果想 (--) 的话最好先判断再减。
注意操作对象……
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<set>
#define INF 1e18
using namespace std;
int read(){
int x=0;
char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x;
}
const int N = 200005;
typedef long long ll;
int n,m,k;
int w[N];
ll s[N];
struct data{
int a,l;
bool operator < (const data &b) const{ return a<b.a; }
}d[N];
ll st1[N][18],st2[N][18];
set<int> Set;
set<int>::iterator it;
int mp[N],Log[N];
ll Min1(int l,int r) {
if(l>r || l<0 || r>k) return INF;
int len=r-l+1,j=Log[len];
return min(st1[l][j],st1[r-(1<<j)+1][j]);
}
ll Min2(int l,int r) {
if(l>r || l<0 || r>k) return INF;
int len=r-l+1,j=Log[len];
return min(st2[l][j],st2[r-(1<<j)+1][j]);
}
int main()
{
n=read(); m=read();
for(int i=1;i<n;i++) w[i]=read();
for(int i=1;i<n;i++) s[i]=s[i-1]+w[i];
int t=0,cur=1;
for(int i=1;i<=n;i++){
if(i==cur) Log[i]=t++,cur*=2;
else Log[i]=t-1;
}
while(m--){
k=read();
for(int i=1;i<=k;i++) d[i].a=read(),d[i].l=read(),Set.insert(d[i].a);
sort(d+1,d+1+k);
for(int i=1;i<=k;i++) mp[d[i].a]=i;
for(int i=1;i<=k;i++) st1[i][0]=d[i].l+s[n-1]-s[d[i].a-1];
for(int i=1;i<=k;i++) st2[i][0]=d[i].l+s[d[i].a-1];
for(int j=1;j<18;j++){
int len=(1<<j);
for(int i=1;i+len-1<=k;i++){
st1[i][j]=min(st1[i][j-1],st1[i+len/2][j-1]);
st2[i][j]=min(st2[i][j-1],st2[i+len/2][j-1]);
}
}
ll ans=0;
for(int i=1;i<=k;i++){
int l=d[i].a,r=n,mid,bon,lm,rm;
while(l<r){
mid=(l+r+1)>>1;
it=Set.upper_bound(2*mid-d[i].a);
if(it!=Set.begin()) bon=mp[*(--it)]; else bon=0;
it=Set.upper_bound(mid);
if(it!=Set.begin()) lm=mp[*(--it)]; else lm=i;
it=Set.lower_bound(mid);
if(it!=Set.end()) rm=mp[*it]; else rm=k+1;
if(bon>0 && d[bon].a+d[i].a==mid*2 && st1[i][0]-(s[n-1]-s[mid-1])==st2[bon][0]-s[mid-1]){
if(Min1(i+1,lm)>st1[i][0] && Min2(rm,bon-1)-s[mid-1]>st1[i][0]-(s[n-1]-s[mid-1])) l=mid;
else r=mid-1;
}
else if(Min1(i+1,lm)>st1[i][0] && Min2(rm,bon)-s[mid-1]>st1[i][0]-(s[n-1]-s[mid-1])) l=mid;
else r=mid-1;
}
ans+=l-d[i].a+1;
l=1;r=d[i].a;
while(l<r){
mid=(l+r)>>1;
it=Set.lower_bound(2*mid-d[i].a);
if(it!=Set.end()) bon=mp[*it]; else bon=k+1;
it=Set.upper_bound(mid);
if(it!=Set.begin()) lm=mp[*(--it)]; else lm=0;
it=Set.lower_bound(mid);
if(it!=Set.end()) rm=mp[*it]; else rm=i;
if(bon<=k && d[bon].a+d[i].a==mid*2 && st1[bon][0]-(s[n-1]-s[mid-1])==st2[i][0]-s[mid-1]) l=mid+1;
else if(Min2(rm,i-1)>st2[i][0] && Min1(bon,lm)-(s[n-1]-s[mid-1])>st2[i][0]-s[mid-1]) r=mid;
else l=mid+1;
}
ans+=d[i].a-l;
}
printf("%lld
",ans);
//clear
for(int i=1;i<=k;i++){
Set.erase(d[i].a);
mp[d[i].a]=0;
}
}
return 0;
}