题目:
算答案的时候要二分!
这样的话,就是对于询问位置 x ,二分出一个最小的 mid 使得 [ x-mid , x+mid ] 里包含所有种类的商店。
判断一个区间里包含所有种类商店的方法是对于每种商店,记录每个这种商店的同类型前驱;然后看看 [ x+mid+1 , INF ] 里所有种类商店的前驱最小值是不是 < x+mid 就行了。
实现方法就是对于每个种类开一个 set 维护该种类商店的所有位置,再对所有种类开一个线段树维护这个区间里的各种商店的前驱最小值;
新增一个 x 位置的商店,就改一下线段树 x 位置的值,再改一下 x 的同类型后继位置的线段树的值。删除一个商店也类似。
因为可能同一时刻同种商店在同一个位置,所以用 multiset ;因为同一时刻不同商店可能在同一个位置,导致同一个位置有不同的前驱值,所以线段树每个叶子开 可删堆 或 set 维护该位置所有前驱,取其中的最小值作为线段树该点的值。
对于无解的情况要特判。可以用一个 可删堆 记录所有种类的最靠前的商店位置,只要在加入或删除商店的时候看看它是不是 set 里最靠前元素即可。
这样是 nlog2n 的。自己实现得很不好,TLE。
#include#include #include #include #include #include #define pb push_backusing namespace std;int rdn(){ int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){ if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar(); return fx?ret:-ret;}int Mn(int a,int b){ return a b?a:b;}const int N=3e5+5,M=3*N,M2=N<<1;//M:time M2:posint n,k,Q,R,tp[M],tot,ls[M<<1],rs[M<<1];int p0,ans[N];struct Sp{ int x,t,a,b;}t[N];struct Node{ int x,t,id; Node(int x=0,int t=0):x(x),t(t) {} bool operator< (const Node &b)const { return t vt[M<<1];struct Tr{ int R,ls[M2<<1],rs[M2<<1],mn[M2<<1]; multiset st[N]; multiset ::iterator it,it2; priority_queue ,greater > q[M2],dq[M2]; priority_queue q2,dq2; void build(int l,int r,int cr) { mn[cr]=R; if(l==r)return; int mid=l+r>>1; ls[cr]=++tot; build(l,mid,ls[cr]); rs[cr]=++tot; build(mid+1,r,rs[cr]); } void init() { for(int i=1;i<=n;i++) tp[++R]=t[i].x; for(int i=1;i<=Q;i++) tp[++R]=qr[i].x; sort(tp+1,tp+R+1); R=unique(tp+1,tp+R+1)-tp-1; for(int i=1;i<=n;i++) t[i].x=lower_bound(tp+1,tp+R+1,t[i].x)-tp; for(int i=1;i<=Q;i++) qr[i].x=lower_bound(tp+1,tp+R+1,qr[i].x)-tp; R++; tot=1; build(1,R,1); for(int i=1;i<=k;i++) st[i].insert(0), st[i].insert(R); for(int i=1;i<=k;i++) q2.push(R); for(int i=1;i<=k;i++) q[R].push(0);// } void frs(int u) { while(dq[u].size()&&dq[u].top()==q[u].top()) q[u].pop(), dq[u].pop(); } void mdfy(int l,int r,int cr,int p,int k,bool fx) { if(l==r) { if(fx)dq[l].push(k); else q[l].push(k); frs(l); mn[cr]=q[l].size()?q[l].top():R; return; } int mid=l+r>>1; if(p<=mid)mdfy(l,mid,ls[cr],p,k,fx); else mdfy(mid+1,r,rs[cr],p,k,fx); mn[cr]=Mn(mn[ls[cr]],mn[rs[cr]]); } int qry(int l,int r,int cr,int L,int R) { if(l>=L&&r<=R)return mn[cr]; int mid=l+r>>1, ret=R; if(L<=mid)ret=qry(l,mid,ls[cr],L,R); if(mid >1,p1=fnd(tp[x]-mid),p2=fnd(tp[x]+mid+1); int d=qry(1,R,1,p2,R); if(d>=p1&&d >1; ls[cr]=++tot; build(l,mid,ls[cr]); rs[cr]=++tot; build(mid+1,r,rs[cr]);}void ins(int l,int r,int cr,int L,int R,Node k){ if(l>=L&&r<=R){vt[cr].pb(k);return;} int mid=l+r>>1; if(L<=mid)ins(l,mid,ls[cr],L,R,k); if(mid >1; dfs(l,mid,ls[cr]); dfs(mid+1,r,rs[cr]); } for(int i=0,lm=vt[cr].size();i
还可以变成 nlogn 。就是把 “二分答案” 和 “线段树判断二分值” 合起来。
就是直接在线段树上走。走到一个节点 ( l , mid , r ) ,判断接下来往哪个孩子走,即答案会在 [ l , mid ] 还是 [ mid+1 , r ] 。
如果询问位置 x 在 [ mid+1 , r ] 里面,就直接往右孩子走即可。
否则想让答案尽量小,就先看看答案能不能在 [ x , mid ] 里面。因为范围越大越容易合法,所以判断一下答案能不能是 mid 即可。
如果 mid 不合法,就有 mn( mid+1 , R ) < max{ 1 , x - ( mid - x ) } ;也就是 mn( mid+1 , R ) < 1 || mn( mid+1 , R ) + mid < 2*x 。
至于 mn( mid+1 , R ) ,如果当前的右孩子 rs 的范围是 [ mid+1 , R ] 的话,直接用线段树上节点存的值 mn[ ] 即可;
如果右孩子的右边界不到 R 的话,可以知道之前一定有某一次,右边界从 =R 变成 <R 了,即走了左孩子;只要在每次走左孩子的时候,把那时的 mn[ rs ] 累计到 tmn 上,用 min{ tmn , mn[ rs ] } 判断即可。
这样最终走到的 l==r 的位置就是答案区间的右端点,返回 l - x 即可。
也因为这样,如果出现过的最大位置是 mx 的话,线段树的范围应该到 2*mx 才行;比如询问点在最右边,某商店在最左边,答案区间的右端点就会大概是 2*mx 。
而且不用把线段树分治的那棵线段树建出来,直接把商店拆成一个加入操作和一个删除操作,同询问一起按时间排序即可。
按时间排序的时候注意第二关键字是操作类型。同一时间应该先 删除/加入 再询问。
也不用把坐标离散化,动态开点即可。这样要注意没有 ls 或 rs 的情况,可以赋初值 mn[ 0 ] = INF 来解决。
判断无解的好方法是记录一个 Tpcnt 表示有多少种商店出现了;只要在加入或删除商店的时候看看它是不是该种类的唯一一个商店就能维护了。
#include#include #include #include using namespace std;int rdn(){ int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){ if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar(); return fx?ret:-ret;}int Mx(int a,int b){ return a>b?a:b;}int Mn(int a,int b){ return a