专栏文章

支配点对小记

算法·理论参与者 2已保存评论 1

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
1 条
当前快照
1 份
快照标识符
@minvu9ln
此快照首次捕获于
2025/12/02 09:11
3 个月前
此快照最后确认于
2025/12/02 09:11
3 个月前
查看原文

支配点对

给定一棵 nn 个结点的树,有边权。有 mm 次询问,每次询问求有几个不同的 dep(LCA(x,y))\mathrm{dep}(\mathrm{LCA}(x,y)),其中 lx,yrl \le x,y \le r
1n105,1m5×1051 \le n \le 10^5,1 \le m \le 5 \times 10^5
看起来是一个非常难的问题。
对于相同 LCA(x,y)\mathrm{LCA}(x,y) 对应的 dep(LCA(x,y))\mathrm{dep}(\mathrm{LCA(x,y)}) 肯定一样,我们先考虑计数不同 LCA(x,y)\mathrm{LCA}(x,y)
对于两个点对 (a,b),(x,y)(a,b),(x,y),其中 xaby,LCA(a,b)=LCA(x,y)x \le a \le b \le y,\mathrm{LCA}(a,b)=\mathrm{LCA}(x,y),我们称 (x,y)(x,y) 支配了 (a,b)(a,b)。可以发现被其他点对支配的点对一定对答案没有贡献。因为它们的 LCA\mathrm{LCA} 一样,而且当我们询问到 (a,b)(a,b) 就一定可以询问到 (x,y)(x,y)
我们称没有被其他点对支配的点对为支配对。则我们只需要考虑支配对的贡献。
考虑如何找支配对。
我们钦定一个 kk,考虑怎样的两个点 x,yx,y 对应的 LCA(x,y)=k\mathrm{LCA}(x,y)=k
容易发现当 x,yx,ykk 的不同子树中,它们的 LCA(x,y)\mathrm{LCA}(x,y) 一定为 kk。否则它们的 LCA(x,y)\mathrm{LCA}(x,y) 一定不为 kk
依次枚举 kk 的某一子树,将前面算过的子树中的结点插入到一个 set 里。若 xx 在这一子树,我们只需要在 set 中找前驱后继就是支配对。
但是这样复杂度是 O(n2logn)\mathcal {O}(n^2 \log n)
考虑 kk 由下至上枚举,然后处理 LCA(x,y)=k\mathrm{LCA}(x,y)=k 的情况是 set 直接从重儿子继承过来,这样整个重儿子的结点都不需要重新加入 set。
对于一个结点,因为重儿子子树结点数大于所有轻儿子,依次合并轻儿子的过程相当于启发式合并。轻儿子中所有结点所处集合的大小至少翻倍,故对于每一个结点,我们最多合并 O(logn)\mathcal{O}(\log n) 次。
这也说明了支配对的数量是 O(nlogn)\mathcal {O}(n \log n) 的。
对于每个支配对及其贡献 (x,y,t)(x,y,t),我们按 xx 排序。对于每一组询问 (l,r)(l,r),我们按 ll 排序。然后从 nn11 扫描线,插入 x=ix=i 的贡献,计算 l=il=i 的询问。当然上述过程可以全部反着来。
我们希望 lxyrl \le x \le y \le r,对于 lxl \le x 显然一定满足。考虑 yry \le r,因为我们是在数颜色,所以只需要找最小的 yy 考虑。
维护每种 tt 对应的最小的 yy,插入到树状数组即可快速查询。对于 dep(LCA(x,y))\mathrm{dep}(\mathrm{LCA}(x,y)),简单替换贡献即可。另外本题需要对 dep(x)\mathrm{dep}(x) 离散化。
复杂度是 O(nlog2n+mlogn)\mathcal {O}(n \log^2 n + m \log n)
CPP
#include<bits/stdc++.h>
#define int long long
#define rd read()
#define gc pa == pb && (pb = (pa = buf) + fread(buf, 1, 100000, stdin), pa == pb) ? EOF : *pa++
using namespace std;
static char buf[100000], * pa(buf), * pb(buf);
inline int read()
{
	register int x=0,ss=1,s=gc;
	while(!isdigit(s)&&s!='-')s=gc;
	if(s=='-')ss=-1,s=gc;
	while(isdigit(s))x=(x<<1)+(x<<3)+(s^48),s=gc;
	return ss*x;
}
const int N=100005,M=500005;
int n,m,ans[M],son[N],siz[N],dep[N],pos[N];
map<int,int> mp;
vector<pair<int,int> > v[N],p[N],q[N];
//p存下所有支配对
set<int> s;
struct BIT
{
	int c[N];
	inline void add(int x,int y){while(x<=n+3)c[x]+=y,x+=(x&-x);}
	inline int ask(int x){int s=0;while(x)s+=c[x],x^=(x&-x);return s;}
}T;
inline void get(int x,int w)
{
	auto i=s.lower_bound(x);
	if(i!=s.end())p[x].push_back({*i,w});
	if(i!=s.begin())--i,p[*i].push_back({x,w});
}
inline void find(int x,int f,int w){get(x,w);for(auto [i,j]:v[x])if(i!=f)find(i,x,w);}
inline void add(int x,int f){s.insert(x);for(auto [i,j]:v[x])if(i!=f)add(i,x);}
inline void dfs(int x,int f)
{
	siz[x]=1;
	for(auto [i,j]:v[x])if(i!=f)
	{
		dep[i]=dep[x]+j,dfs(i,x),siz[x]+=siz[i];
		if(siz[son[x]]<siz[i])son[x]=i;
	}
}
inline void sol(int x,int f)
{
	for(auto [i,j]:v[x])if(i!=f&&i!=son[x])sol(i,x),s.clear();	
	if(son[x])sol(son[x],x);
	s.insert(x),p[x].push_back({x,dep[x]});
	for(auto [i,j]:v[x])if(i!=f&&i!=son[x])find(i,x,dep[x]),add(i,x);
}
signed main()
{
	n=rd,m=rd;
	for(int i=1,x,y,w;i<n;i++)
	{
		x=rd,y=rd,w=rd;
		v[x].push_back({y,w});
		v[y].push_back({x,w});
	}
	dfs(1,0);
	int cnt=0;
	for(int i=1;i<=n;i++)
	{
		if(!mp[dep[i]])mp[dep[i]]=++cnt;
		dep[i]=mp[dep[i]];
	}
	for(int i=1;i<=cnt;i++)pos[i]=n+1;
	sol(1,0);
	for(int i=1,l;i<=m;i++)l=rd,q[l].push_back({rd,i});
	for(int i=n;i>=1;i--)
	{
		for(auto [j,k]:p[i])T.add(pos[k],-1),pos[k]=min(pos[k],j),T.add(pos[k],1);
		for(auto [j,k]:q[i])ans[k]=T.ask(j);
	}
	for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
	return 0;
}

给定一棵 nn 个结点的树,有 mm 次询问,每次询问有多少个二元组 L,R\mathrm{L},\mathrm{R},满足 lLRrl\le \mathrm{L}\le \mathrm{R}\le r,且对任意 LaxayR\mathrm{L}\le a_x\le a_y\le \mathrm{R},有 x,yx,y 在树上的最近公共祖先 zz 满足 LazR\mathrm{L}\le a_z\le \mathrm{R}
1n,m2×1051\le n,m\le 2\times 10^5
考虑单个点对 (x,y)(x,y) 使得那些区间不合法。
z=LCA(x,y)z=\mathrm{LCA}(x,y),考虑以下情况:
1.对于 axayaza_x \le a_y \le a_z,不合法的区间包含 ax,aya_x,a_y,而不包含 aza_z。故不合法的区间 [l,r][l,r] 满足 lax,r[ay,az)l \le a_x,r \in [a_y,a_z)
2.对于 axazaya_x \le a_z \le a_y,它对于所有区间都不存在约束。
3.对于 azaxaya_z \le a_x \le a_y,易知 l(az,ax],rayl \in (a_z,a_x],r \ge a_y
然后将所有点对拿下来做扫描线是 O(n2logn)\mathcal {O}(n^2 \log n) 的。
继续考虑哪些点对没有用。钦定一个 zz,然后 aza_z 不变,x,yx,y 分别在 zz 的两个不同子树。
可以发现对于一个 xx,只有最小的 ayaxa_y \ge a_x 和最大的 ayaxa_y \le a_xyy 有用。我们将 aia_i 丢到 set 里找前驱后继即可,启发式合并复杂度正确。可以发现支配对的数量是 O(nlogn)\mathcal {O}(n \log n)
然后将每个支配对对应的 rr 挂在序列上,相当于区间加,区间历史 00 的数量。
维护区间最小值 xx,最小值出现次数 yy,历史 00 出现次数 zz
每经过一个时刻,对于 x=0x=0 的结点 zy+zz \gets y+z
然后复杂度是 O(nlog2n+mlogn)\mathcal {O}(n \log^2 n + m \log n)
CPP
#include<bits/stdc++.h>
#define ll long long
#define rd read()
#define gc pa == pb && (pb = (pa = buf) + fread(buf, 1, 100000, stdin), pa == pb) ? EOF : *pa++
using namespace std;
static char buf[100000], * pa(buf), * pb(buf);
inline int read()
{
	unsigned register int x=0,s=gc;
	while(!isdigit(s))s=gc;
	while(isdigit(s))x=(x<<1)+(x<<3)+(s^48),s=gc;
	return x;
}
const int N=200005;
int n,m,a[N],son[N],siz[N];
ll ans[N];
struct node{int x,y,z;};
vector<node> k[N];
vector<pair<int,int> > q[N];
vector<int> v[N];
set<int> s;
inline void insert(int x,int y,int z)
{
	if(y<z)k[y].push_back({1,x,1}),k[z].push_back({1,x,-1});
	if(z<x)k[y].push_back({z+1,x,1});
}
inline void dfs(int x,int f)
{
	siz[x]=1;
	for(int i:v[x])if(i!=f)
	{
		dfs(i,x),siz[x]+=siz[i];
		if(siz[son[x]]<siz[i])son[x]=i;
	}
}
inline void get(int x,int z)
{
	auto i=s.lower_bound(x);
	if(i!=s.end())insert(x,*i,z);
	if(i!=s.begin())--i,insert(*i,x,z);
}
inline void find(int x,int f,int w){get(a[x],w);for(int i:v[x])if(i!=f)find(i,x,w);}
inline void add(int x,int f){s.insert(a[x]);for(int i:v[x])if(i!=f)add(i,x);}
inline void sol(int x,int f)
{
	for(int i:v[x])if(i!=f&&i!=son[x])sol(i,x),s.clear();
	if(son[x])sol(son[x],x);
	s.insert(a[x]);
	for(int i:v[x])if(i!=f&&i!=son[x])find(i,x,a[x]),add(i,x);
}
struct seg
{
	int mn[N<<2],cnt[N<<2],tag[N<<2],htg[N<<2];ll sum[N<<2];
	inline void pushup(int id)
	{
		int l=id<<1,r=id<<1|1;
		mn[id]=min(mn[l],mn[r]),sum[id]=sum[l]+sum[r];
		cnt[id]=cnt[l]*(mn[l]==mn[id])+cnt[r]*(mn[r]==mn[id]);
	}
	inline void push1(int id,int v){tag[id]+=v,mn[id]+=v;}
	inline void push2(int id,int v){htg[id]+=v,sum[id]+=1ll*cnt[id]*v;}
	inline void pushdown(int id)
	{
		if(tag[id])push1(id<<1,tag[id]),push1(id<<1|1,tag[id]),tag[id]=0;
		if(htg[id])
		{
			if(mn[id]==mn[id<<1])push2(id<<1,htg[id]);
			if(mn[id]==mn[id<<1|1])push2(id<<1|1,htg[id]);
			htg[id]=0;
		}
	}
	inline void build(int id,int l,int r)
	{
		if(l==r)return cnt[id]=1,void();
		int mid=l+r>>1;build(id<<1,l,mid),build(id<<1|1,mid+1,r),pushup(id);
	}
	inline ll ask(int id,int l,int r,int x,int y)
	{
		if(x<=l&&y>=r)return sum[id];
		int mid=l+r>>1,ans=0;pushdown(id);
		if(x<=mid)ans+=ask(id<<1,l,mid,x,y);
		if(y>mid)ans+=ask(id<<1|1,mid+1,r,x,y);
		return ans;
	}
	inline void add(int id,int l,int r,int x,int y,int v)
	{
		if(x<=l&&y>=r)
		{
			if(v)return push1(id,v);
			if(!mn[id])return push2(id,1);
			return;
		}
		int mid=l+r>>1;pushdown(id);
		if(x<=mid)add(id<<1,l,mid,x,y,v);
		if(y>mid)add(id<<1|1,mid+1,r,x,y,v);
		pushup(id);
	}
}T;
signed main()
{
	n=rd,m=rd;T.build(1,1,n);
	for(int i=1;i<=n;i++)a[i]=rd;
	for(int i=2;i<=n;i++)v[rd].push_back(i);
	dfs(1,0),sol(1,0);
	for(int i=1,l,r;i<=m;i++)l=rd,r=rd,q[r].push_back({l,i});
	for(int i=1;i<=n;i++)
	{
		for(auto [l,r,w]:k[i])T.add(1,1,n,l,r,w);
		T.add(1,1,n,1,i,0);
		for(auto [l,fl]:q[i])ans[fl]=T.ask(1,1,n,l,i);
	}
	for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
	return 0;
}

给定一个序列 {a}\{a\},每次询问给定一个区间 [l,r][l,r],求 minli,jraiaj\min_{l \le i,j \le r}\lvert a_i-a_j \rvert
1n105,1m3×1051 \le n \le 10^5,1\le m \le 3 \times 10^5
绝对值非负,分两次处理,每次只考虑 ajaia_j-a_i 的值,其中 ajaia_j-a_i 非负,并且有 i<ji<j。第二次处理前,我们对于所有 aiaia_i \gets -a_i
那么从前向后枚举 jj,对于每个 jj 寻找支配对 (i,j)(i,j)
(i,j)(i,j) 是一个有贡献的点对。我们希望找一个 k<ik<i,使得 (k,j)(k,j) 是一个有贡献的点对。
如上图,容易知道 (k,j)(k,j) 不应被 (k,i)(k,i)(i,j)(i,j) 支配。首先容易知道 ai,akaja_i,a_k \le a_j
对于 (i,j)(i,j) 的影响,有 ajak<ajaia_j-a_k < a_j-a_i,得 ak>aia_k>a_i。所以 ai<akaja_i< a_k\le a_j
对于 (k,i)(k,i) 的影响,有 ajak<akaia_j-a_k < a_k-a_i,所以 ak>ai+aj2a_k > \frac{a_i+a_j}{2}
所以 k<ik<i 是最大的满足 ak(ai+aj2,aj]a_k \in (\frac{a_i+a_j}{2},a_j] 的数。插到线段树二分,然后 jkj \gets k 继续找,由于每次值域折半,所以支配对是 O(nlogv)\mathcal {O}(n \log v ) 的。
询问挂在 rr 上扫描线可以做到 O(nlognlogv+mlogn)\mathcal {O}(n \log n \log v + m \log n)
CPP
#include<bits/stdc++.h>
#define N 300005
#define int long long
#define rd read()
#define gc pa == pb && (pb = (pa = buf) + fread(buf, 1, 100000, stdin), pa == pb) ? EOF : *pa++
using namespace std;
static char buf[100000], * pa(buf), * pb(buf);
inline int read()
{
	register int x=0,s=gc;
	while(!isdigit(s))s=gc;
	while(isdigit(s))x=(x<<1)+(x<<3)+(s^48),s=gc;
	return x;
}
const int inf=1000000001,V=inf-1;
int n,m,cnt,a[N],ans[N];
vector<pair<int,int> > q[N],v[N];
struct node{int ls,rs,mx;}t[N*20];
inline int New(){t[++cnt]={0,0,0};return cnt; }
struct seg
{
	int root=0;
	inline void pushup(int id){t[id].mx=max(t[t[id].ls].mx,t[t[id].rs].mx);}
	inline void modify(int &id,int l,int r,int x,int v)
	{
		if(!id)id=New();
		if(l==r)return t[id].mx=max(t[id].mx,v),void();
		int mid=l+r>>1;
		if(x<=mid)modify(t[id].ls,l,mid,x,v);
		else modify(t[id].rs,mid+1,r,x,v);
		pushup(id);
	}
	inline int ask(int id,int l,int r,int x,int y)
	{
		if(!id)return 0;
		if(x>r||y<l) return 0;
		if(x<=l&&y>=r)return t[id].mx;
		int mid=l+r>>1;
		return max(ask(t[id].ls,l,mid,x,y),ask(t[id].rs,mid+1,r,x,y));
	}
	inline int find(int l,int r){return ask(root,1,V,l,r);}
}T;
struct BIT
{
	int c[N];
	inline void clear(){memset(c,0x3f,sizeof(c));}
	inline int ask(int x)
	{
		int s=inf;
		while(x<=n)s=min(s,c[x]),x+=(x&-x);
		return s;
	}
	inline void chk(int x,int y){while(x)c[x]=min(c[x],y),x^=(x&-x);}
}G;
inline void sol()
{
	cnt=0,a[0]=inf,T.root=0;
	for(int j=1;j<=n;j++)
	{
		int i=T.find(1,a[j]);
		while(a[i]<a[j])v[j].push_back({i,a[j]-a[i]}),i=T.find((a[i]+a[j]>>1)+1,a[j]);
		if(a[i]==a[j])v[j].push_back({i,0});
		T.modify(T.root,1,V,a[j],j);
	}
}
signed main()
{
	n=rd;for(int i=1;i<=n;i++)a[i]=rd;m=rd;
	for(int i=1,l,r;i<=m;i++)l=rd,q[rd].push_back({l,i});
	sol();for(int i=1;i<=n;i++)a[i]=inf-a[i];
	sol(),G.clear();
	int cnt=0;
	for(int i=1;i<=n;i++)cnt+=v[i].size();
	for(int i=1;i<=n;i++)
	{
		for(auto [l,w]:v[i])G.chk(l,w);
		for(auto [l,id]:q[i])ans[id]=G.ask(l);
	}
	for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
	return 0;
}

给定一个带边权的有 nn 个点的树,mm 次询问,每次询问求起点和终点在 [l,r][l,r] 内的最短树上路径。
1n2×105,1m1061 \le n \le 2 \times 10^5,1\le m \le 10^6
树上路径,考虑点分治。对于当前根 rr,先找出所有的 dx=dis(x,r)d_x=\mathrm{dis}(x,r)。则对于任意一条路径 (x,y)(x,y),我们用 dx+dyd_x+d_y 表示其长度。
对于在 rr 同一颗子树的 x,yx,y,我们会将其路径算长,但当我们继续递归,一定会算到正确的路径,所以这样做对答案没有影响。
于是我们有若干点对 (x,dx)(x,d_x),按 xx 增序排列。每次求 lx,yrl \le x,y \le r 的最小的 dx+dyd_x+d_y,我们不可能每次询问都来求一边 rmq。考虑找支配点对。
对于一个支配点对 (x,y)(x,y),显然满足 max(dx,dy)mini(x,y)di\max(d_x,d_y)\le \min _{i \in (x,y)}d_i,否则 xxyy 可以向内缩,得到更优解。
如果 dxdyd_x \le d_ydxd_xyy 之前第一个比 dyd_y 小的元素。如果 dydxd_y \le d_xdyd_yxx 之后第一个比 dxd_x 小的元素。
正反分别做单调栈维护,每个点贡献 O(1)\mathcal{O}(1) 个支配对。加上点分治,一共 O(nlogn)\mathcal{O}(n \log n) 个支配对。
扫描线,二维数点即可。时间复杂度 O(nlog2n+qlogn)\mathcal{O}(n \log^2 n +q \log n),空间复杂度 O(nlogn+q)\mathcal{O}(n \log n+q)
CPP
#include<bits/stdc++.h>
#define int long long
#define rd read()
#define gc pa == pb && (pb = (pa = buf) + fread(buf, 1, 100000, stdin), pa == pb) ? EOF : *pa++
using namespace std;
static char buf[100000], * pa(buf), * pb(buf);
inline int read()
{
	register int x=0,s=gc;
	while(!isdigit(s))s=gc;
	while(isdigit(s))x=(x<<1)+(x<<3)+(s^48),s=gc;
	return x;
}
const int N=200005,M=1000005,inf=1e16;
bool vis[N];
int n,m,rt,sz,mx[N],siz[N],ans[M];
vector<pair<int,int> > v[N],q[N],p[N],d;
inline void chkmax(int &x,int y){x=(x<y?y:x);}
inline void chkmin(int &x,int y){x=(x>y?y:x);}
inline void find(int x,int f)
{
	siz[x]=1,mx[x]=0;
	for(auto [i,j]:v[x])
		if(i!=f&&!vis[i])find(i,x),siz[x]+=siz[i],chkmax(mx[x],siz[i]);
	chkmax(mx[x],sz-siz[x]);
	if(mx[x]<mx[rt])rt=x;
}
inline void get(int x,int l,int f)
{
	if(vis[x])return;
	d.push_back({x,l});
	for(auto [i,j]:v[x])if(i!=f)get(i,l+j,x);
}
inline void sol(int x)
{
	if(vis[x])return;
	vis[x]=1;
	for(auto [i,j]:v[x])get(i,j,x);
	d.push_back({x,0}),sort(d.begin(),d.end());	
	stack<pair<int,int> > st;
	for(auto [i,j]:d)
	{
		while(st.size()&&st.top().second>j)st.pop();
		if(st.size())p[i].push_back({st.top().first,st.top().second+j});
		st.push({i,j});
	}
	while(st.size())st.pop();
	reverse(d.begin(),d.end());
	for(auto [i,j]:d)
	{
		while(st.size()&&st.top().second>j)st.pop();
		if(st.size())p[st.top().first].push_back({i,st.top().second+j});
		st.push({i,j});
	}
	d.clear();
	for(auto [i,j]:v[x])if(!vis[i])rt=0,sz=siz[i],find(i,x),sol(rt);	
}
struct BIT
{
	int c[N];
	inline void chk(int x,int y){while(x)chkmin(c[x],y),x^=(x&-x);}
	inline int ask(int x){int s=LONG_LONG_MAX;while(x<=n)chkmin(s,c[x]),x+=(x&-x);return s;}
}G;
signed main()
{
	n=rd;for(int i=1,x,y,w;i<n;v[x].push_back({y,w}),v[y].push_back({x,w}),++i)x=rd,y=rd,w=rd;
	m=rd;for(int i=1,l;i<=m;++i)l=rd,q[rd].push_back({l,i});
	mx[0]=n+1,rt=0,sz=n,find(1,0),sol(rt),memset(G.c,0x3f,sizeof(G.c));
	for(int i=1;i<=n;++i)
	{
		for(auto [l,k]:p[i])G.chk(l,k);
		for(auto [l,id]:q[i])ans[id]=G.ask(l);
	}
	for(int i=1;i<=m;++i)cout<<(ans[i]>inf?-1:ans[i])<<'\n';
	return 0;
}

给你一个长度为 nn 的序列 {a}\{a\},共有 qq 次询问,每次询问给你一个区间 [l,r][l,r],求满足 li,j,krl\le i,j,k\le rai,aj,aka_i,a_j,a_k 为边长可以构成三角形,ai+aj+aka_i+a_j+a_k 的最小值。
1n2.5×105,1q5×105,1ai1071\le n\le 2.5\times 10^5,1\le q\le 5\times 10^5,1\le a_i\le 10^7
值域倍增分块,其中第 mm 个块 Bm=[2m,2m+1)B_m=[2^{m},2^{m+1})。容易知道在同一个块中容易选的 33 个均可以组成三角形。
若区间 [l,r][l,r]BkB_k 中存在 3\ge 3 个数,那么可以将其中最小的三个数之和贡献到答案。这个用线段树维护。对于其他的符合条件的 k>kk'>k,他们显然无用。
找区间中某个块的数的个数,可以对每个块 BmB_m 开一个前缀和记录。同时,我们还可以选若干个 <2k< 2^k 的数,按选这种数的个数讨论。
  1. 若三角形中包含 2\ge 2<2k<2^k 的数。
    因为块的数量是 O(logv)\mathcal{O}(\log v) 的,而 [0,k)[0,k) 中每个块只有 O(1)\mathcal{O}(1) 个元素,故 <2k<2^k 的数在区间 [l,r][l,r] 只有 O(logv)\mathcal{O}(\log v) 个。
    将这些元素全部取出。具体的,取出元素的过程分别求 ll 的后继与 rr 的前驱,对于每个块,这可以在 O(n)\mathcal{O}(n) 的时间内求出。将这些元素和 BkB_k 的最小元素一起从小到大排列,取出元素判一判即可,记为 [t1,,tm][t_1 ,\ldots ,t_m]
    对于一种三角形选法,其最长的两条边在 {tm}\{t_m\} 必然相邻。证明考虑最大边更大不优。所以当我们在 <2k<2^k 的数中选了 =2=2 个时,其最长边为 BkB_k[l,r][l,r] 的最小值。
    设目前考虑的点对为 (i,j1,j)(i,j-1,j),则有 tjtj1<tit_j-t_{j-1}<t_i。如果存在 j>jj'>j,满足 tjtj1tjtj1t_{j'}-t_{j'-1}\ge t_j-t_{j-1},那么 jj' 一定无用。因为其对应的 titit_{i'}\ge t_i
    于是只用考虑前缀最小的 (tjtj1)(t_j-t_{j-1}),它肯定是递减的,所以直接双指针即可。
    这部分的时间复杂度为 O(nlogv)\mathcal{O}(n \log v)
  2. 若三角形中包含 =1= 1<2k<2^k 的数。
    判掉有数在 Bk+1B_{k+1} 的情况。选了一个 <2k<2^k 的数表示我们选了 22BkB_k 中的数。
    枚举所有 <2k< 2^k 的数 aea_e,如果我们选了它。则我们选的另外两数 ai,aja_i,a_j 要满足 aiaj<ae\lvert a_i-a_j\rvert <a_e
    根据 1. 中的结论,ai,aja_i,a_j 一定是排序后相邻的两个数。所以当 max(ai,aj)\max(a_i,a_j) 最小时,(ai+aj)(a_i+a_j) 也最小。
    将块中所有数从小到大排序,记为 [t1,,tm][t_1 ,\ldots ,t_m],依次插入。考虑统计支配对。
    若当前插入了 tpt_p,新增支配对一定要选 pp,所以这些点对的 max(ai,aj)=tp\max(a_i,a_j)=t_p 相等,只需要让 aiaj\lvert a_i-a_j\rvert 最小,这是区间最近点对。用 CF765F 的方法线段树二分,可以在 O(nlognlogv)\mathcal{O}(n \log n \log v) 的时间找到所有支配对。
    于是现在有了 O(nlogv)\mathcal{O}(n \log v) 个支配对 (I,J)(I,J),扫描线。然后每次会找 lI,Jr,aIaJ<ael \le I,J \le r, \lvert a_I-a_J\rvert<a_e 的最小 (aI+aJ)(a_I+a_J)。三维偏序,此时我们的复杂度不低于 O(nlognlog2v+qlognlogv)\mathcal{O}(n \log n \log^2 v +q \log n \log v),看起来不可以过。原因是维数过大。
    考虑什么样的询问 [l,r][l,r],最小值可以枚举到 aea_e,若 aL,aRa_L,a_R 分别是 ee 同块前后两个数,则 L<lr<RL < l\le r <R。我们称 [L+1,R1][L+1,R-1]ee 的最小值区间。
    因为每个块中最小值区间长度总和是 O(n)\mathcal{O}(n) 的,所以最小值区间长度一共是 O(nlogv)\mathcal{O}(n \log v) 的。如果在这些区间找 aIaJ<ae\lvert a_I-a_J\rvert<a_e 的支配对,一共有 O(nlog2v)\mathcal{O}(n \log ^2 v) 个。然后以 (i,j,ai+aj+ae)(i,j,a_i+a_j+a_e) 的形式扫描线即可。
    扫描线的结构中修改与查询的次数分别为 O(nlog2v)\mathcal{O}(n \log^2 v)O(q)\mathcal{O}(q)。使用 O(1)O(nϵ)\mathcal{O}(1)-\mathcal{O}(n^{\epsilon}) 的迭代分块即可。
    这部分的时间复杂度为 O(nlog2v+qnϵ)\mathcal{O}(n \log^2 v+q n^{\epsilon})
这个做法空间复杂度是 O(nlog2v)\mathcal{O}(n \log^2 v)。是官方题解中的算法 1,比算法 2 好想,但是时空复杂度均劣于算法 2。
我们只需要一边扫描线一边找支配对即可做到 O(nlogv)\mathcal{O}(n \log v) 空间,比算法 2 的 O(n+v)\mathcal{O}(n + v) 还要优。最后空间只跑了 200 MB。
时间上有点慢,但不太卡常。
CPP
#include<bits/stdc++.h>
#define rd read()
#define gc pa == pb && (pb = (pa = buf) + fread(buf, 1, 100000, stdin), pa == pb) ? EOF : *pa++
#define min(x,y) (x<y?x:y)
#define max(x,y) (x>y?x:y)
#define chkmin(x,y) (x=x<y?x:y)
using namespace std;
static char buf[100000], * pa(buf), * pb(buf);
inline int read()
{
	register int x=0,s=gc;while(!isdigit(s))s=gc;
	while(isdigit(s))x=(x<<1)+(x<<3)+(s^48),s=gc;
	return x;
}
const int N=250005,M=500005,LG=24,inf=1e8,V=1e7;
const int B1=21,B2=B1*B1,B3=B1*B1*B1;
const int S1=N/B1+5,S2=N/B2+5,S3=N/B3+5;
int n,m,cnt;
int a[N],b[N],c[N],sc[N],bl[N],p[N];
int p2[N],s2[N],MX[M];
int ql[M],qr[M],ans[M],pos[N],is[M];
vector<int> G[M],P[N],K[N],K2[N];
vector<pair<int,int> > Q[N];
inline bool cmp(int x,int y){return a[x]<a[y];}
inline bool cmp2(int x,int y){return x>y;}
struct SEG
{
	int mx[N<<2];
	inline void modify(int id,int v){id=pos[id],mx[id]=v;for(id>>=1;id;id>>=1)mx[id]=v;}
	inline int ask(int id,int l,int r,int x,int v)
	{
		if(mx[id]<v||l>x)return 0;
		if(l==r)return l;int mid=l+r>>1,X=0;
		if(mx[id<<1|1]>=v)X=ask(id<<1|1,mid+1,r,x,v);
		if(!X&&mx[id<<1]>=v)X=ask(id<<1,l,mid,x,v);
		return X;
	}
	inline int find(int l,int MX){return ask(1,1,n,l,MX);}
}fd;
struct BT
{
	int L1[S1],R1[S1],L2[S2],R2[S2],L3[S3],R3[S3];
	int mn1[S1],mn2[S2],mn3[S3],b1[N],b2[N],b3[N],C[N];
	inline void build()
	{
		memset(C,0x3f,sizeof(C)),memset(mn1,0x3f,sizeof(mn1));
		memset(mn2,0x3f,sizeof(mn2)),memset(mn3,0x3f,sizeof(mn3));
		for(int i=1;i<=n;++i)b1[i]=(i-1)/B1+1,b2[i]=(i-1)/B2+1,b3[i]=(i-1)/B3+1;
		for(int i=1;i<=b1[n];++i)L1[i]=R1[i-1]+1,R1[i]=min(n,i*B1);
		for(int i=1;i<=b2[n];++i)L2[i]=R2[i-1]+1,R2[i]=min(n,i*B2);
		for(int i=1;i<=b3[n];++i)L3[i]=R3[i-1]+1,R3[i]=min(n,i*B3);
	}
	inline int ask(int x)
	{
		int res=inf;
		for(int i=b3[n];i>b3[x];--i)chkmin(res,mn3[i]);
		for(int i=b2[R3[b3[x]]];i>b2[x];--i)chkmin(res,mn2[i]);
		for(int i=b1[R2[b2[x]]];i>b1[x];--i)chkmin(res,mn1[i]);
		for(int i=R1[b1[x]];i>=x;--i)chkmin(res,C[i]);
		return res;
	}
	inline void chk(int x,int y){chkmin(C[x],y),chkmin(mn1[b1[x]],y),chkmin(mn2[b2[x]],y),chkmin(mn3[b3[x]],y);}
}sl;
struct node
{
	int m1,m2,m3,mx;
	inline node operator +(const node &o)const
	{
		node res;
		res.mx=max(mx,o.mx);
		if(m1<o.m1)
		{
			res.m1=m1;
			if(m2<o.m1)res.m2=m2,res.m3=m3<o.m1?m3:o.m1;
			else res.m2=o.m1,res.m3=m2<o.m2?m2:o.m2;
		}
		else
		{
			res.m1=o.m1;
			if(m1<o.m2)res.m2=m1,res.m3=m2<o.m2?m2:o.m2;
			else res.m2=o.m2,res.m3=m1<o.m3?m1:o.m3;
		}
		return res;
	}
}t[N<<2],it;
struct seg
{
	inline void build(int id,int l,int r)
	{
		if(l==r)return t[id]={b[l],inf,inf,b[l]==inf?-inf:b[l]},void();
		int mid=l+r>>1;build(id<<1,l,mid),build(id<<1|1,mid+1,r);
		t[id]=t[id<<1]+t[id<<1|1];
	}
	inline node ask(int id,int l,int r,int x,int y)
	{
		if(x>r||y<l)return {inf,inf,inf,-inf};
		if(x<=l&&y>=r)return t[id];int mid=l+r>>1;
		return ask(id<<1,l,mid,x,y)+ask(id<<1|1,mid+1,r,x,y);
	}
}T;
inline void gt(int id,int l,int r)
{
	if(l==r)return pos[l]=id,void();
	int mid=l+r>>1;gt(id<<1,l,mid),gt(id<<1|1,mid+1,r);
}
signed main()
{
	n=rd,m=rd,sc[n+1]=n+1,sc[n+2]=n+1,a[n+1]=a[0]=inf,sl.build(),gt(1,1,n);
	for(int i=1;i<=n;bl[i]=__lg(a[i]),p[i]=i,s2[i]=n+1,++i)a[i]=rd;sort(p+1,p+n+1,cmp);
	for(int i=1;i<=m;++i)ql[i]=rd,qr[i]=rd,ans[i]=inf,Q[qr[i]].push_back({i,ql[i]});
	for(int i=0;i<=24;++i)
	{
		for(int j=1;j<=n;++j)
			if(bl[j]==i)b[j]=a[j],c[j]=c[j-1]+1;
		else b[j]=inf,c[j]=c[j-1];
		T.build(1,1,n);
		for(int j=n;j>=1;--j)sc[j]=(a[j]==b[j]?j:sc[j+1]);
		for(int j=1;j<=n;++j)
			if(a[j]==b[j])s2[j]=sc[sc[j+1]+1],p2[sc[sc[j+1]+1]]=j;
		for(int j=1,cnt,x,y,l,r;j<=m;++j)if(!is[j])
		{
			l=ql[j],r=qr[j],cnt=c[r]-c[l-1];
			if(cnt==1)G[j].push_back(a[sc[l]]);
			else if(cnt==2)
			{
				x=sc[l],y=sc[sc[l]+1];if(a[x]>a[y])swap(x,y);
				G[j].push_back(a[x]),G[j].push_back(a[y]);
			}
			else if(cnt>=3)
			{
				is[j]=i,it=T.ask(1,1,n,l,r),MX[j]=it.mx;
				chkmin(ans[j],it.m1+it.m2+it.m3),G[j].push_back(it.m1);
			}
		}else if(i==is[j]+1)
		{
			l=ql[j],r=qr[j],cnt=c[r]-c[l-1];
			if(cnt>0)
			{
				int MN=T.ask(1,1,n,l,r).m1;
				for(int e:G[j])if(e>MN-MX[j]){chkmin(ans[j],e+MN+MX[j]);break;}
			}
		}
	}
	for(int i=1,j,mn,h,P;i<=m;++i)
	{
		mn=inf,P=G[i].size(),j=1;
		while(j<P-1&&G[i][j-1]+G[i][j]<=G[i][j+1])++j;
		for(;j<G[i].size()-1;++j)
		{
			h=G[i][j+1]-G[i][j];
			if(mn>=h)
			{
				while(P>0&&G[i][P-1]>h)--P;
				if(P<j&&G[i][P]>h)chkmin(ans[i],G[i][P]+G[i][j]+G[i][j+1]);mn=h;
			}
		}
	}
	for(int J=1,i,j;J<=n;fd.modify(j,a[j]),++J)
	{
		j=p[J],i=fd.find(j,1<<bl[j]);
		while(a[i]<=a[j])P[j].push_back(i),i=fd.find(j,(a[i]+a[j]>>1)+1);
	}
	memset(fd.mx,0,sizeof(fd.mx));
	for(int J=1,i,j;J<=n;fd.modify(n-j+1,a[j]),++J)
	{
		j=p[J],i=n-fd.find(n-j+1,1<<bl[j])+1;
		while(a[i]<=a[j])P[i].push_back(j),i=n-fd.find(n-j+1,(a[i]+a[j]>>1)+1)+1;
	}
	for(int i=1;i<=n;++i)stable_sort(P[i].begin(),P[i].end(),cmp2);
	for(int e,E=n,R;E>=1;--E){e=p[E],R=s2[e];for(int j=e+1;j<R;++j)K[j].push_back(e);}
	for(int i=1,h;i<=n;++i)
	{
		for(int j=p2[i]+1;j<i;++j)for(int I:P[j])
		{
			if(I<=p2[i])break;
			if(i!=I&&a[i]>abs(a[I]-a[j])&&bl[i]<bl[I])sl.chk(min(i,I),a[I]+a[i]+a[j]);
		}
		for(int j:P[i])
		{
			h=abs(a[i]-a[j]);
			for(int e:K[i])
			{
				if(a[e]<=h)break;
				if(e!=j&&bl[e]<bl[i])sl.chk(min(e,j),a[e]+a[i]+a[j]);
			}
		}
		for(auto [id,l]:Q[i])chkmin(ans[id],sl.ask(l));
	}
	for(int i=1;i<=m;++i)
		if(ans[i]>=inf)cout<<"yumi!\n";
		else cout<<ans[i]<<'\n';
	return 0;
}

评论

1 条评论,欢迎与作者交流。

正在加载评论...