bzoj4530: [Bjoi2014]大融合
题意
N<=1e5个点,Q<=1e5个操作。
支持加一条边(u,v)(保证图是森林)、询问经过边(u,v)的简单路径条数(保证(u,v)存在)。
题解
/*本来是想当作lct维护子树信息例题的,然而一眼线段树分治,于是写了一下,以为可以很快码完…。
结果发现代码奇长无比,运行奇慢无比,而且还调了半天QAQ
大概是写的姿势不对吧*/
如果不算询问的那条边,那么答案就是当前u的联通块大小*v的联通块大小。
所以每条边原本的出现时间(加边的时间~结束)会被询问分隔成几段,所有边的总段数依然是Q级别的。
于是我们愉快地线段树分治顺便并查集维护一下siz就好啦。
代码
#include
#define N 100005
#define V 2000006
using namespace std;
typedef long long ll;
struct edge
{int u,v;}e[N];
mapint >mp;
mapint >::iterator it;
int n,q,tmp,lst[N],qnum[N],cct,cnt,tot,
to[V],hd[V],lk[N<<2],f[N],siz[N],t[20],
no[20][N<<1],ff[20][N<<1],d=-1;
bool used[2][20][N];
void ins(int k,int l,int r,int ll,int rr,int v)
{
if(ll>rr)return;
if(l==ll&&r==rr)
{to[++tot]=v,hd[tot]=lk[k],lk[k]=tot;return;}
int mid=l+r>>1;
ins(k<<1,l,mid,ll,rr1|1,mid+1,r,ll<=mid?mid+1:ll,rr,v);
}
int getr(int x)
{
if(f[x]==x)return x;
if(!used[1][d][x])
no[d][t[d]]=x,ff[d][t[d]++]=f[x],used[1][d][x]=1;
return f[x]=getr(f[x]);
}
void dfs(int k,int l,int r)
{
d++;
int x,y;
for(int i=lk[k];i;i=hd[i])
{
x=getr(e[to[i]].u),y=getr(e[to[i]].v);
if(!used[0][d][y])
no[d][t[d]]=y,ff[d][t[d]++]=-siz[y],used[0][d][y]=1;
if(!used[1][d][x])
no[d][t[d]]=x,ff[d][t[d]++]=f[x],used[1][d][x]=1;
siz[y]+=siz[x],f[x]=y;
}
if(l^r)
(dfs(k<<1,l,x=l+r>>1),dfs(k<<1|1,x+1,r));
else
{
x=getr(e[qnum[l]].u),y=getr(e[qnum[l]].v);
printf("%lld\n",(ll)siz[x]*
siz[y]);
}
while(t[d]--)
(ff[d][t[d]]<0)?
(siz[no[d][t[d]]]=-ff[d][t[d]],
used[0][d][no[d][t[d]]]=0):
(f[no[d][t[d]]]=ff[d][t[d]],
used[1][d][no[d][t[d]]]=0);
t[d--]=0;
}
char op[N][2];
ll tp;
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=q;i++)
{
scanf("%s%d%d",op[i],&e[i].u,&e[i].v);
if(e[i].u>e[i].v)swap(e[i].u,e[i].v);
if(op[i][0]=='Q')qnum[++cnt]=i;
}
for(int i=1;i<=q;i++)
{
tp=(ll)e[i].u*n+e[i].v;
if(op[i][0]=='A')
mp.insert(make_pair(tp,i)),lst[i]=cct+1;
else
{
cct++;
tmp=mp.find(tp)->second;
ins(1,1,cnt,lst[tmp],cct-1,tmp);
lst[tmp]=cct+1;
}
}
for(int i=1;i<=q;i++)
if(lst[i])ins(1,1,cnt,lst[i],cnt,i);
for(int i=1;i<=n;i++)siz[f[i]=i]=1;
dfs(1,1,cnt);
}