CF286D - Tourists
首先我们考虑先把区间处理掉,也就是对于每个 \(y\) 轴区间,只保留在这个区间上最早出现的进行贡献,使得每个区间只有一段城墙会贡献到。这个可以离散化然后线段树来处理。
其次,我们发现,对于在时间 \(t_i\) 上出现的 \([l_i,r_i]\) 区间(这里我们说的都是指离散后 \([a,a+1)\) 的区间,也就是 \([l_i,r_i\) 实际上覆盖的实数区间是 \([l_i,r_i+1\)),它会对某一个时间区间内出发的人造成贡献。
具体而言,通过边界分析可得,一个时间 \(t_i\) 出现的 \([l_i,r_i]\) 区间,会对 \(t_i-r_i\) 出发的人造成 \(1\) 的贡献(恰好在最后一格墙出现)。对 \(t_i-l_i\) 出发的人造成 \(r_i-l_i+1\) 的贡献(恰好遇到所有墙)。那么,就是对 \([t_i-r_i-1,t_i-l_i]\) 加上一个公差为 \(0\),首项为 \(0\) 的等差数列。而对 \([t_i-l_i+1,+\infty)\) 加上一个常数 \(r_i-l_i+1\)。
我们就把问题转化成了区间加等差数列和区间单点查询。不过时间区间要离散化。而最终查询到的线段树叶子节点是一个时间区间和一个一次函数,直接一次函数求值即可。
int n,m,q,l[200005],r[200005],t[200005],to[200005];
int x[200005],b[400005];
namespace Segtree1{
int T[1600005];
inline void add(int i,int L,int R,int x,int l,int r){
T[i]=min(T[i>>1],T[i]);
if(l>R||L>r)return;
if(L<=l&&r<=R){
T[i]=min(T[i],x);
return;
}add(i<<1,L,R,x,l,(l+r)>>1);
add(i<<1|1,L,R,x,((l+r)>>1)+1,r);
}
inline void solve(int i,int L,int R){
T[i]=min(T[i>>1],T[i]);
if(L==R){
if(T[i]!=1e18){
l[++n]=b[L],r[n]=b[R+1]-1,t[n]=T[i];
}return;
}int mid=(L+R)>>1;
solve(i<<1,L,mid);
solve(i<<1|1,mid+1,R);
}
}
inline void restruct(){
rp(i,n)b[++m]=l[i],b[++m]=r[i];
sort(b+1,b+1+m);
m=unique(b+1,b+1+m)-b-1;
rep(i,0,(m<<2))Segtree1::T[i]=1e18;
rp(i,n){
l[i]=lower_bound(b+1,b+1+m,l[i])-b;
r[i]=lower_bound(b+1,b+1+m,r[i])-b;
Segtree1::add(1,l[i],r[i]-1,t[i],1,m);
}n=0;
Segtree1::solve(1,1,m);
}
bool flag=0;
struct node{
int l,r,len;
ll a,k;
}sg[1200005];
inline void init(int i,int l,int r){
sg[i].l=l,sg[i].r=r,sg[i].a=0,sg[i].k=0;
sg[i].len=b[r+1]-b[l];
if(l==r)return;
init(i<<1,l,(l+r)>>1);
init(i<<1|1,((l+r)>>1)+1,r);
}
inline void pushdown(int i){
if(sg[i].l==sg[i].r)return;
sg[i<<1].a+=sg[i].a,sg[i<<1|1].a+=(sg[i].a+sg[i<<1].len*sg[i].k);
sg[i<<1].k+=sg[i].k,sg[i<<1|1].k+=sg[i].k;
sg[i].a=0,sg[i].k=0;
}
inline void add(int i,int l,int r,ll a,ll k){
pushdown(i);
if(sg[i].l>r||sg[i].r<l)return;
if(sg[i].l>=l&&sg[i].r<=r){
sg[i].a+=a+k*(b[sg[i].l]-b[l]),sg[i].k+=k;
return;
}
add(i<<1,l,r,a,k);
add(i<<1|1,l,r,a,k);
}
inline ll qry(int i,int x){
pushdown(i);
if(b[sg[i].l]>x||b[sg[i].r+1]<=x)return 0;
if(sg[i].l==sg[i].r){
return sg[i].a+sg[i].k*(x-b[sg[i].l]+1);
}return qry(i<<1,x)+qry(i<<1|1,x);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>q>>n;
rp(i,n)cin>>l[i]>>r[i]>>t[i];
restruct();m=0;
rp(i,n){
int L=t[i]-r[i],R=t[i]-l[i];
l[i]=L,r[i]=R;
b[++m]=L,b[++m]=R;
}b[++m]=0;
sort(b+1,b+1+m);
m=unique(b+1,b+1+m)-b-1;
b[m+1]=1e18;
init(1,1,m);
rp(i,n){
l[i]=lower_bound(b+1,b+1+m,l[i])-b;
r[i]=lower_bound(b+1,b+1+m,r[i])-b;
add(1,l[i],r[i]-1,0,1);
add(1,r[i],m,b[r[i]]-b[l[i]]+1,0);
}
rp(i,q){
cin>>x[i];
cout<<qry(1,x[i])<<endl;
}
return 0;
}
//Crayan_r
但是这个做法是可以优化的。具体而言,因为我们两个线段树的操作都是静态的,所以都可以离线下来做。
首先,我们可以对第一个问题将贡献区间排序,然后用 set
记录当前的最小值,直接把每个子区间处理出来,省掉第一个线段树。
然后,我们把加等差数列改成差分数列区间加,前缀和。然后用扫描线的思想优化,记录当前每往后一格的贡献 \(add\) 以及前一位的答案 \(sum\)。每次就是把相邻的 \(q_i\) 之间区间的贡献记录,加上这个区间内新的加入的等差数列的公差以及贡献。而区间加等差数列后缀加末项改成后缀加两个等差数列,一个公差为 \(1\),另一个是 \(-1\),然后我们就可以做了。码量小很多,但是跑的并没有更快。
int n,m,q,l[200005],r[200005],t[200005],b[400005],x[200005];
vt<int>ope[400005];
vt<pii>v;
inline void restruct(){
rp(i,n)b[++m]=l[i],b[++m]=r[i];
sort(b+1,b+1+m);
m=unique(b+1,b+1+m)-1-b;
rp(i,n){
l[i]=lower_bound(b+1,b+1+m,l[i])-b;
r[i]=lower_bound(b+1,b+1+m,r[i])-b-1;
ope[l[i]].pb(i);
ope[r[i]+1].pb(-i);
}set<pii>cur;n=0;
rep(i,1,m){
for(auto j:ope[i]){
if(j>0)cur.insert({t[j],j});
else cur.erase({t[-j],-j});
}
if(cur.size()){
v.pb({cur.begin()->first-b[i+1]+1,1});
v.pb({cur.begin()->first-b[i]+1,-1});
}
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>q>>n;
rp(i,n)cin>>l[i]>>r[i]>>t[i];
restruct();
sort(v.begin(),v.end());
reverse(v.begin(),v.end());
x[0]=-1e9;
ll sum=0,add=0;
rp(i,q){
cin>>x[i];
sum+=(x[i]-x[i-1])*add;
while(v.size()&&v.back().first<=x[i]){
int y=v.back().first,s=v.back().second;
v.pop_back();add+=s,sum+=s*(x[i]-y+1);
}cout<<sum<<endl;
}
return 0;
}
//Crayan_r