dtoi4543「HNOI2016」最小公倍数
题意:
给定一张 $ N $ 个顶点 $ M $ 条边的无向图(顶点编号为 $ 1,2, \cdots ,n $),每条边上带有权值。所有权值都可以分解成 $ 2^a \cdot 3^b $ 的形式。
现在有 $ q $ 个询问,每次询问给定四个参数 $ u $、$ v $、$ a $ 和 $ b $,请你求出是否存在一条顶点 $ u $ 到 $ v $ 之间的路径,使得路径依次经过的边上的权值的最小公倍数为 $ 2^a \cdot 3^b $ 。
$1 \leq n,q \leq 50000,\ 1 \leq m \leq 100000,\ 0 \leq a,b \leq 10^9 $。
题解:
显然,此题题意就是问存不存在一条路径,使得两种类型的最大值分别等于给定值。
假如只有一种类型的话,可以先从小到大排序,按数值大小顺序加入操作和询问,用并查集维护连通块和最大值就行了。
如果有两种类型,可以先按照一维从小到大排序,按顺序加入操作和询问,但是处理询问的时候,显然并不是所有的边都能被用上,只有另一种类型同时也小于等于询问的值才能用上,这有点像二维偏序,但是分治似乎没有办法解决这个问题。
换句话说,现在需要解决的问题就是动态维护权值前 $i$ 小的边所形成的并查集(由于第一种权值排序了,所以只要管第二种权值),似乎非常困难。但是通过观察时限和数据范围可以猜测需要根号算法。
先尝试将 $m$ 个权值从小到大分成 $B$ 块,每一个块维护一个并查集,第$i$ 个并查集表示前 $i-1$ 块中的边所形成的并查集。那么每次插入操作相当于把后面一段连续的块的并查集都进行一次合并操作。查询相当于拿着某一块的并查集,把剩下的边给暴力一个一个加入进去,再判断。这样只需要一个可以支持后退操作的并查集就行了,效率为 $O(n*max(m/B,B)*\log_2n)$。然而我写出来的代码不知道为什么块开成 $\sqrt{m}$ 过不了,开成 $\sqrt{m}*log_2m$才能过,应该是因为查询操作次数少吧。
#include<cstdio> #include<map> #include<algorithm> #include<cmath> #include<cstdlib> using namespace std; int n,m,q,f[320][50002],maxa[320][50002],maxb[320][50002],fk,len; short dep[320][50002]; bool fl[50002],ans[50002]; map<int,int>wz; typedef struct{ int u,v,a,b,num; }P; typedef struct{ int x,fx,dx,ax,bx; }PP; typedef struct{ int u,v,a,b; }PPP; PPP t[200002]; bool cmp(P aa,P bb){ return (aa.a<bb.a || aa.a==bb.a && aa.num<bb.num); } bool cmpp(PPP aa,PPP bb){ return (aa.b<bb.b); } P p[200002]; PP dl[10000]; int find(int x,int y){ if (f[x][y]==y)return y; return find(x,f[x][y]); } void jl(int nw,int x){ dl[++len].x=x; dl[len].fx=f[nw][x];dl[len].dx=dep[nw][x]; dl[len].ax=maxa[nw][x];dl[len].bx=maxb[nw][x]; } void hb(int nw,int x,int y,int a,int b,bool u){ int fx=find(nw,x),fy=find(nw,y); if (u){jl(nw,fx);jl(nw,fy);} if (fx==fy) { maxa[nw][fy]=max(maxa[nw][fy],a); maxb[nw][fy]=max(maxb[nw][fy],b); return; } if (dep[fx]>dep[fy])swap(fx,fy); dep[nw][fy]=max(dep[nw][fy],(short)(dep[nw][fx]+1)); maxa[nw][fy]=max(maxa[nw][fy],max(maxa[nw][fx],a)); maxb[nw][fy]=max(maxb[nw][fy],max(maxb[nw][fx],b)); f[nw][fx]=fy; } int main() { scanf("%d%d",&n,&m); for (int i=1;i<=m;i++){scanf("%d%d%d%d",&p[i].u,&p[i].v,&p[i].a,&p[i].b);t[i].b=p[i].b;} scanf("%d",&q); for (int i=1;i<=q;i++){scanf("%d%d%d%d",&p[m+i].u,&p[m+i].v,&p[m+i].a,&p[m+i].b);p[m+i].num=i;} sort(p+1,p+(m+q)+1,cmp);sort(t+1,t+m+1,cmpp);fk=sqrt(m*log2(m)); for (int i=1;i<=m;i++)if (!wz.count(t[i].b))wz[t[i].b]=i; for (int i=1;(i-1)*fk+1<=m;i++) for (int j=1;j<=n;j++) { f[i][j]=j;dep[i][j]=0;maxa[i][j]=maxb[i][j]=-1; } for (int i=1;i<=m+q;i++) if (!p[i].num) { int nw=wz[p[i].b];wz[p[i].b]++; t[nw].a=p[i].a;t[nw].u=p[i].u;t[nw].v=p[i].v; fl[nw]=1; for (int j=1;(j-1)*fk+1<=m;j++) if ((j-1)*fk+1>nw)hb(j,p[i].u,p[i].v,p[i].a,p[i].b,0); } else { len=0;int nw=0; for (int j=1;(j-1)*fk+1<=m;j++) { nw=j; if (t[j*fk].b>p[i].b)break; } for (int j=(nw-1)*fk+1;j<=nw*fk&&j<=m;j++) if (fl[j] && t[j].b<=p[i].b)hb(nw,t[j].u,t[j].v,t[j].a,t[j].b,1); int fx=find(nw,p[i].u); if (fx==find(nw,p[i].v) && maxa[nw][fx]==p[i].a && maxb[nw][fx]==p[i].b)ans[p[i].num]=1; for (int j=len;j>=1;j--) { f[nw][dl[j].x]=dl[j].fx; dep[nw][dl[j].x]=dl[j].dx; maxa[nw][dl[j].x]=dl[j].ax; maxb[nw][dl[j].x]=dl[j].bx; } } for (int i=1;i<=q;i++)if (ans[i])puts("Yes");else puts("No"); return 0; }