题目传送门:http://hihocoder.com/problemset/problem/1629
题目大意:给出一幅n个点,m条边的无向图,然后给出q组询问。每组询问给定一个区间[L,R],问[L,R]中有多少点对可以相互到达。可以到达的要求是只能走[L,R]中的点。不超过5组数据,n,m<=50000,q<=100000。
题目分析:这题应该算是bzoj4537的弱化版吧。
所谓分块,就是在暴力的基础上优化,所以我们先考虑怎么暴力。可以枚举一个左端点L,然后将右端点R从左往右扫,并用一个并查集维护答案。合并两个大小分别为x和y的集合时,对答案的新贡献就是x*y。然后离线回答[L,R]的询问。
上述方法的缺点就是要枚举的L太多,时间是 O(m2α(n)) 的。假设我们统计了每个点的度数,现在将度数和为 m−−√ 的一些点分成一块,然后将每个块的末尾作为关键点(意思是对于块[a,b],满足[a,b-1]的度数和小于 m−−√ ,[a,b]的度数和大于等于 m−−√ ,然后b为关键点),只枚举这些关键点作为左端点。
当我们枚举第i个关键点为左端点的时候,同时处理所有左端点在(第i-1个关键点,第i个关键点],右端点在第i个关键点及之后的询问,将这些询问按右端点从小到大排序。假设当前的询问区间是[L,R],则我们先将[第i个关键点,R]的边加进并查集,这个部分可以用启发式合并+路径压缩。然后将[L,第i个关键点)的边用启发式合并加进并查集(不能用路径压缩,因为要撤销),并用一个栈记录操作。处理完询问后将栈里的操作还原。
如果某个询问没有跨越任何一个关键点,就说明区间内的边数小于 m−−√ ,直接用启发式合并+路径压缩的并查集处理即可。处理完了之后再将修改了的元素暴力初始化。
那么这样时间复杂度是多少呢?由于启发式合并+路径压缩的并查集,运行时间非常小,可以认为接近均摊 O(1) 。那么总时间就是 nm−−√+qm−−√log(n) 。这里要注意一个小细节:因为我们是按点分块的,所以如果有度数为0的点,要用链表跳过这个点。否则如果有很多度数为0的点连在一起,每一次询问就都要扫这些无用的点,就不能保证时间为 m−−√log(n) 。
但其实如果将块的大小控制在 mlog(n)−−−−−√ ,使得存在 mlog(n)−−−−−−−√ 个块的话,就能将时间降为 (n+q)mlog(n)−−−−−−−√ 。然而一件很神奇的事情是,我第一次写代码的时候,将块的大小打成了 mlog(n)−−−−−−−√ ,居然2200ms就过了。而我后来发现了这个问题,将大小改成了 mlog(n)−−−−−√ ,就TLE了QAQ……
CODE:
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=50100;
struct edge
{
int obj;
edge *Next;
} e[maxn<<1];
edge *head[maxn];
int cur;
int num[maxn];
int a[maxn];
int Prev[maxn];
int cnt1;
int fst[maxn];
int id[maxn];
int cnt2;
struct data
{
int L,R,Time;
} ask[maxn<<1];
int ans[maxn<<1];
int fa[maxn];
int Size[maxn];
int tp;
int fa1[maxn];
int Size1[maxn];
int tp1=0;
int sak[maxn];
int tail;
int t,n,m,q;
int step;
void Add(int x,int y)
{
cur++;
e[cur].obj=y;
e[cur].Next=head[x];
head[x]=e+cur;
}
bool Comp1(data x,data y)
{
return x.Lbool Comp2(data x,data y)
{
return x.Rint Up(int x)
{
if (x==fa[x]) return x;
return (fa[x]=Up(fa[x]));
}
void Merge(int x,int y)
{
x=Up(x);
y=Up(y);
if (x==y) return;
if (Size[x]int Find(int x)
{
if (x==fa[x]) return x;
return Find(fa[x]);
}
void Push(int x,int y)
{
x=Find(x);
y=Find(y);
if (x==y) return;
if (Size[x]void Clear()
{
while (tail)
{
int x=sak[tail];
Size[ fa[x] ]-=Size[x];
tp-=( Size[x]*Size[ fa[x] ] );
fa[x]=x;
tail--;
}
}
int Up1(int x)
{
if (x==fa1[x]) return x;
return (fa1[x]=Up1(fa1[x]));//一开始这里的Up1打成了Up!!!
}
void Merge1(int x,int y)
{
x=Up1(x);
y=Up1(y);
if (x==y) return;
if (Size1[x]int main()
{
freopen("1629.in","r",stdin);
freopen("1629.out","w",stdout);
scanf("%d",&t);
while (t--)
{
scanf("%d%d%d",&n,&m,&q);
cur=-1;
for (int i=1; i<=n; i++) head[i]=NULL,num[i]=id[i]=Prev[i]=0;
for (int i=1; i<=m; i++)
{
int x,y;
scanf("%d%d",&x,&y);
Add(x,y);
Add(y,x);
num[x]++;
num[y]++;
}
cnt1=0;
for (int i=1; i<=n; i++) if (num[i]) a[++cnt1]=i,Prev[i]=i;
for (int i=1; i<=n; i++) if (!Prev[i]) Prev[i]=Prev[i-1];
cnt2=0;
step=(int)floor( sqrt( (double)(2*m)*(double)(log(n)/0.3) )+1e-5 );
int now=0;
for (int i=1; i<=n; i++)
{
now+=num[i];
if (now>=step)
{
fst[++cnt2]=i;
id[i]=cnt2;
now=0;
}
}
if (fst[cnt2]for (int i=n; i>=1; i--) if (!id[i]) id[i]=id[i+1];
for (int i=1; i<=n; i++) fa1[i]=i,Size1[i]=1;
for (int i=1; i<=q; i++) scanf("%d%d",&ask[i].L,&ask[i].R),ask[i].Time=i;
sort(ask+1,ask+q+1,Comp1);
int last=1;
for (int i=1; i<=cnt2; i++)
{
int x=fst[i],y=last;
while ( y<=q && id[ ask[y].L ]<=i ) y++;
if (last==y) continue;
sort(ask+last,ask+y,Comp2);
for (int j=1; j<=n; j++) fa[j]=j,Size[j]=1;
tp=0;
int ta=x-1;
for (int j=last; jif (ask[j].R>=x)
{
while (tafor (edge *p=head[ta]; p; p=p->Next)
{
int to=p->obj;
if ( x<=to && to<=ask[j].R ) Merge(ta,to);
}
}
tail=0;
for (int k=Prev[x-1]; k>=ask[j].L; k=Prev[k-1])
for (edge *p=head[k]; p; p=p->Next)
{
int to=p->obj;
if ( ask[j].L<=to && to<=ask[j].R ) Push(k,to);
}
ans[ ask[j].Time ]=tp;
Clear();
}
else //当区间没有跨越任何一个关键点的时候,暴力计算答案!!!
{
for (int k=ask[j].L; k<=ask[j].R; k++)
for (edge *p=head[k]; p; p=p->Next)
{
int to=p->obj;
if ( ask[j].L<=to && to<=ask[j].R ) Merge1(k,to);
}
ans[ ask[j].Time ]=tp1;
for (int k=ask[j].L; k<=ask[j].R; k++)
{
fa1[k]=k,Size1[k]=1;
for (edge *p=head[k]; p; p=p->Next)
{
int to=p->obj;
fa1[to]=to,Size1[to]=1;
}
}
tp1=0;
}
last=y;
}
for (int i=1; i<=q; i++) printf("%d\n",ans[i]);
}
return 0;
}