Stamp Rally
最大值最小,可以二分,然后并查集看能不能到z个点
但是询问过多,并且发现每次二分要加入的点并不是所有的m条边
于是就考虑整体二分
并查集的处理是重点:
对于一般的dfs分治树,
我们必然要在处理前面部分回溯来的时候,递归右子树之前,左子树并查集的信息必须保留。
但是还要删除当前层的部分并查集的合并操作。
如果直接路径压缩+暴力重构的话,到了后面,每次重构就是O(n)的了,直接N^2了。
为了支持删除,就考虑按秩合并。
合并成功的时候,用一个栈记录pair,然后删除的时候弹栈删除即可。相当于时光倒流
当然,之前的合并一定不能撤销的。
出错点:
1.l打成1海星。。。
2.如果当前区间没有决策位置的话,可以直接返回,但是这些[l,r]编号的边不能扔掉,必须直接加进去。
#include<bits/stdc++.h> #define il inline #define reg register int #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=1e5+5; const int M=1e5+5; struct node{ int x,y; }e[M]; struct que{ int x1,x2,z; int id,ans; }q[M],b[M]; int fa[N]; int sz[N]; int op[N]; int n,m; int fin(int x){ return fa[x]==x?x:fin(fa[x]); } pair<int,int>sta[N]; int top; void merge(int x,int y,bool fl){ int k1=fin(x),k2=fin(y); if(k1!=k2){ if(sz[k1]>sz[k2]) swap(k1,k2); fa[k1]=k2; sz[k2]+=sz[k1]; if(fl)sta[++top]=make_pair(k1,k2); } } void dele(int x,int y){ fa[x]=x; sz[y]-=sz[x]; } void divi(int l,int r,int le,int ri){ //cout<<" l r "<<l<<" "<<r<<" : "<<le<<" and "<<ri<<endl; if(l>r) return; if(le>ri){ for(reg i=l;i<=r;++i){ merge(e[i].x,e[i].y,0); } return; } if(l==r){ for(reg i=le;i<=ri;++i){ q[i].ans=l; } merge(e[l].x,e[l].y,0); return; } int mid=(l+r)>>1; for(reg i=l;i<=mid;++i){ merge(e[i].x,e[i].y,1); } //cout<<" mid "<<mid<<endl; int pre=le-1,bac=ri+1; for(reg i=le;i<=ri;++i){ int k1=fin(q[i].x1),k2=fin(q[i].x2); int tmp=0; if(k1!=k2){ tmp=sz[k1]+sz[k2]; }else{ tmp=sz[k1]; } if(tmp>=q[i].z) b[++pre]=q[i]; else b[--bac]=q[i]; } for(reg i=le;i<=ri;++i){ q[i]=b[i]; } //cout<<" pre "<<pre<<" bac "<<bac<<" top "<<top<<endl; //if(top){ while(top){ dele(sta[top].first,sta[top].second); --top; } //} divi(l,mid,le,pre); divi(mid+1,r,bac,ri); } int main(){ rd(n);rd(m); for(reg i=1;i<=m;++i){ rd(e[i].x);rd(e[i].y); } int que; rd(que); for(reg i=1;i<=que;++i){ rd(q[i].x1);rd(q[i].x2);rd(q[i].z); q[i].id=i; } for(reg i=1;i<=n;++i){ fa[i]=i;sz[i]=1;//;dep[i]=1; } divi(1,m,1,que); for(reg i=1;i<=que;++i){ op[q[i].id]=q[i].ans; } for(reg i=1;i<=que;++i){ printf("%d\n",op[i]); } return 0; } } signed main(){ // freopen("data.in","r",stdin); // freopen("my.out","w",stdout); Miracle::main(); return 0; } /* Author: *Miracle* Date: 2018/12/18 8:37:44 */
还有一种:
你不是暴力重构会TLE吗?但是我一共就需要循环logn次O(m)地把并查集加入
考虑逐层处理
因为对于整体二分,bfs和dfs的顺序都没有问题。
所以bfs处理整体二分,这样,加入就直接加入了,只要暴力重构O(logn)次。
bfs+路径压缩,理论上可以更快一些。