图上路径问题1,题解
按边分块
[HNOI2016] 最小公倍数
题意:
一张图, \(n\) 个点, \(m\) 条边, \(q\) 次询问。
每条边有正整数权值 \(val_u\) ,满足 \(\exists a,b\in \mathbb{N},val_u=2^a\times 3^b\)
每次询问给出一个 \(u,v,a,b\) 表示是否存在一条从 \(u\) 到 \(v\) 的路径(不需要满足简单路径),
该路径上所有边的权值最小公倍数 \(v=2^a\times 3^b\)
思路:
离线询问,按边分块。
记 \(a_e\) 表示边 \(e\) 的权值 \(2\) 的幂次, \(b_e\) 表示其权值 \(3\) 的幂次。
我们把每条边 \(e\) 按照 \(a_e\) 排序,然后对其分块。
每个询问按照 \(b_q\) 为关键字排序。
枚举每个块 \(B\) ,然后取出所有 \(a_q \in a_B\) 的询问,因为所有整块已经满足 \(a_e\le a_q\),所以我们直接按照 \(b_e\) 排序。
枚举每个询问,先加入整块内的 \(b_e\le b_q\) 的边,在散块内的边暴力加入,然后通过栈退回。
我们可以得到代码:
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;
const int N=5e4+5,M=1e5+5;
int n,m,Q,len;
struct edge{int a,b,u,v;}e[M];
struct qry{int a,b,u,v,idx;}q[N],nq[N];
struct dsu{int x,y,a,b,s;}stk[M];
int fa[N],mxa[N],mxb[N],siz[N],top,qcnt;
bool ans[N];
void clr(){
for(int i=1;i<=n;++i)fa[i]=i,siz[i]=1,mxa[i]=-1,mxb[i]=-1;top=0;
}
int getfa(int u){while(fa[u]!=u)u=fa[u];return u;}
void merge(int x,int y,int a,int b){
x=getfa(x),y=getfa(y);
if(siz[x]>siz[y])swap(x,y);
stk[++top]={x,y,mxa[y],mxb[y],siz[y]};
if(x^y){
fa[x]=y;
siz[y]+=siz[x];
}
mxa[y]=max(mxa[y],max(a,mxa[x]));
mxb[y]=max(mxb[y],max(b,mxb[x]));
}
void pop(int ptop=0){
while(top>ptop){
fa [stk[top].x]=stk[top].x;
siz[stk[top].y]=stk[top].s;
mxa[stk[top].y]=stk[top].a;
mxb[stk[top].y]=stk[top].b;
--top;
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;++i)cin>>e[i].u>>e[i].v>>e[i].a>>e[i].b;
cin>>Q;
for(int i=1;i<=Q;++i)cin>>q[i].u>>q[i].v>>q[i].a>>q[i].b,q[i].idx=i;
len=sqrt(m);
sort(e+1,e+1+m,[](edge x,edge y){return x.a<y.a;});
sort(q+1,q+1+Q,[](qry x,qry y){return x.b<y.b;});
for(int i=1;i<=m;i+=len){
clr();
qcnt=0;
for(int j=1;j<=Q;++j)if(e[i].a<=q[j].a&&(i+len>m||e[i+len].a>q[j].a))nq[++qcnt]=q[j];
if(!qcnt)continue;
sort(e+1,e+i,[](edge x,edge y){return x.b<y.b;});
for(int j=1,k=1;j<=qcnt;++j){
for(;k<i&&e[k].b<=nq[j].b;++k)merge(e[k].u,e[k].v,e[k].a,e[k].b);
top=0;
for(int t=i;t<i+len&&t<=m;++t)if(e[t].a<=nq[j].a&&e[t].b<=nq[j].b)merge(e[t].u,e[t].v,e[t].a,e[t].b);
int u=getfa(nq[j].u),v=getfa(nq[j].v);
ans[nq[j].idx]=u==v&&mxa[u]==nq[j].a&&mxb[u]==nq[j].b;
pop();
}
}
for(int i=1;i<=Q;++i)if(ans[i])cout<<"Yes\n";else cout<<"No\n";
return 0;
}
复杂度分析?
我们来分析复杂度,以探究最优块长 \(B\) 。
前面的初始化加排序 \(\mathit{O}(m \log m)\)
枚举块 \(\mathit{O}(\frac{m}{B})\) 。
其循环中:枚举可用询问 \(\mathit{O}(q)\) ,排序 \(\mathit{O}(m\log m)\) ,
遍历可用询问求解累加 \(\mathit{O}(qB\alpha(n)+qm)\)
则因变总复杂度 \(\mathit{O}(\frac{m^2\log m}{B}+qB\alpha(n))\)
当 \(B=m \sqrt{\frac{\log m}{q}}\) 时取得最小复杂度 \(\mathit{O}(m\sqrt{q \log m})\) 。
吗?
我\(T\)飞了(虽然是 \(hack\))
\(B=m\sqrt{\frac{\log m}{q}}\) :
\(B=\frac{m}{\sqrt{q}}\) :
\(B=\sqrt{m}\) :
翻阅洛谷讨论版,找到原因。
[APIO2019] 桥梁
题意:
一张图, \(n\) 个点, \(m\) 条边, \(q\) 次操作。
每条边 \(e\) 有一个权值 \(d_e\) 。
操作有两种:
- 将边 \(e\) 的权值改为 \(r_e\)
- 求以 \(u\) 为起点,经过权值不超过 \(v\) 的边能到达的点的数量。
\(1\le n\le 5\times 10^4,0\le m \le 10^5,1\le q \le 10^5\)
思路:
和上一题差不多,但是是带修(动态)的。
于是我们有暴力思路:
暴力 \(1\) :
很简单:
遇到修改,直接修改。
遇到查询,遍历所有边,求并查集
\(\mathit{O}(qm\alpha(n))\)
暴力 \(2\) :
考虑对所有边和操作排序。
对于没有修改的边,直接合并
有修改的边,遍历所有修改,看时间戳是否小于当前查询的时间戳
\(\mathit{O}(q\log q+m\log m+q^2+q^2\alpha(n))\)
正解:
我们思考一下暴力 \(1\) 与 暴力 \(2\) 劣在何处。
发现一个枚举了所有的边,一个枚举了所有的操作。
我们考虑分块。把操作进行分块,一次处理一个块。
记块大小为 \(B\)
对于一个块:
- 对块内部同暴力 \(2\) 进行求解
- 然后把这个块内的所有修改同暴力 \(1\) 操作
- 枚举下一个块
我们发现,一个块内部最多只有 \(B\) 个修改,那么复杂度为:
\(\mathit{O}(qm+\frac{qm\log m}{B}+qB\alpha(n))\)
取 \(B=\sqrt{m\log m}\)