Stability
啊,极其恶心的题。Tarjan缩点+树剖+LCA+线段树题意:定义两个点之间的稳定度为一个点到另外一个点必须经过的边的条数。给你一个图,Q次操作,每次删除一条边或者查询两个点之间的稳定度。
正向考虑根本无法解决,因为有动态删边的过程,查询两个点的ans几乎就是O(n)。所以我们要反向考虑,题目保证最后的图联通,即最后的图可能是一棵树或者存在环,我们知道一个环中的点的稳定度都为0,关键就在任意两个点之间桥的数量。所以我们需要将最后的图强连通缩点,缩点后肯定是一棵树,每个点代表一个联通块,那么反向考虑, 查询的时候只需查询两个联通块之间的距离(边的条数),如果在一个连通块里面直接输出0。如果是删边操作,因为我们反向考虑, 所以是加边操作,如果两个点在同一个联通块里面不用操作,反之,需要将这条路径上的边全部赋0。也即是借助树链剖分将树转化成线性结构,然后借助线段树或树状数组快速更新或查询区间。我们将原始的边都赋为1,合并只需将两个联通块所在的路径上的边权值赋为0即可。
但我们得知道最后的图,如果用map处理会超时,博主血的教训。网上的题解都是用multiset删除边,于是改了改951ms AC。
const int N=3e4+10;
int n,m,q,tot,c,pos,idx,zd;
int head1[N],head2[N],dfn[N],low[N],belong[N],Stack[N],vis[N];
int fa[N],son[N],top[N],dep[N],num[N],p[N],pr[18][N];
inline int sc()
{
char c;
int ret;
if(c=getchar(),c==EOF) return 0;
while(c!='-'&&(c<'0'||c>'9')) c=getchar();
ret=(c=='-')?0:(c-'0');
while(c=getchar(),c>='0'&&c<='9') ret=ret*10+(c-'0');
return ret;
}
struct node//线段树
{
int l,r,sum;
} a[N<<2];
struct Edge//最终的图
{
int to,next;
} e1[N*4],e2[N*4];
struct EDGE//原图
{
int u,v;
ll val;
} edge[N*4];
struct opera//操作数
{
int op,u,v,ans;
ll val;
} tc[N*4];
typedef pair<int,int> PII;
multiset <PII> M1;
map< ll,int>mp;
void init()
{
zd=tot=c=idx=pos=0;
memset(head1,-1,sizeof(head1));
memset(head2,-1,sizeof(head2));
memset(son,-1,sizeof(son));
memset(vis,0,sizeof(vis));
memset(dfn,0,sizeof(dfn));
M1.clear();
}
void add(int u,int v,int f)//f为1表示最终的图,f为2表示终图缩点后的图
{
if(f==1)
{
e1[tot].to=v,e1[tot].next=head1[u];
head1[u]=tot++;
e1[tot].to=u,e1[tot].next=head1[v];
head1[v]=tot++;
}
else
{
e2[tot].to=v,e2[tot].next=head2[u];
head2[u]=tot++;
e2[tot].to=u,e2[tot].next=head2[v];
head2[v]=tot++;
}
}
void tarjan(int u,int pre)//求最终图的联通分量然后缩点
{
int v;
low[u]=dfn[u]=++idx;
Stack[zd++]=u,vis[u]=1;
for(int i=head1[u]; i+1; i=e1[i].next)
{
v=e1[i].to;
if(v==pre) continue;
if(!dfn[v])
{
tarjan(v,u);
if(low[u]>low[v]) low[u]=low[v];
}
else if(vis[v]&&low[u]>dfn[v]) low[u]=dfn[v];
}
if(dfn[u]==low[u])
{
c++;
do
{
v=Stack[--zd];
vis[v]=0;
belong[v]=c;
}
while(v!=u);
}
}
void Tarjan()
{
for(int i=1; i<=n; i++) if(!dfn[i]) tarjan(i,i);
tot=0;
mp.clear();
//缩点
for(int i=1; i<=n; i++)
{
for(int j=head1[i]; j+1; j=e1[j].next)
{
int v=e1[j].to;
ll tmp1=ll(i)*INF+v;
ll tmp2=ll(v)*INF+i;
if(belong[i]!=belong[v]&&!mp[tmp1]&&!mp[tmp2])
{
add(belong[i],belong[v],2);//构造一颗树
mp[tmp1]=mp[tmp2]=1;
}
}
}
}
void dfs1(int u,int pre,int d)
{
pr[0][u]=pre,dep[u]=d,fa[u]=pre,num[u]=1;
for(int i=head2[u]; i+1; i=e2[i].next)
{
int v=e2[i].to;
if(v==pre) continue;
dfs1(v,u,d+1);
num[u]+=num[v];
if(son[u]==-1||num[v]>num[son[u]]) son[u]=v;
}
}
void dfs2(int u,int tp)
{
top[u]=tp,p[u]=++pos;
if(son[u]==-1) return ;
dfs2(son[u],tp);
for(int i=head2[u]; i+1; i=e2[i].next)
{
int v=e2[i].to;
if(v!=son[u]&&v!=fa[u]) dfs2(v,v);
}
}
void init_lca()//缩点后总共c个点
{
pr[0][belong[1]]=-1;
for(int k=0; k<16; k++)
for(int i=1; i<=c; i++)
if(pr[k][i]<0) pr[k+1][i]=-1;
else pr[k+1][i]=pr[k][pr[k][i]];
}
int lca(int u,int v)
{
if(dep[v]>dep[u]) swap(u,v);
for(int k=0; k<16; k++) if((dep[u]-dep[v])>>k&1) u=pr[k][u];
if(u==v) return u;
for(int i=15; i>=0; i--)
if(pr[i][u]!=pr[i][v])
{
u=pr[i][u];
v=pr[i][v];
}
return pr[0][u];
}
void pushdown(int k)
{
if(a[k].l!=a[k].r&&a[k].sum==0)
a[k*2].sum=a[k*2+1].sum=0;
}
void build(int l,int r,int k)
{
a[k].l=l,a[k].r=r,a[k].sum=0;
if(l==r)
{
a[k].sum=1;
return ;
}
int mid=(l+r)/2;
build(l,mid,2*k);
build(mid+1,r,2*k+1);
a[k].sum=a[k*2].sum+a[k*2+1].sum;
}
void update(int l,int r,int x,int k)//区间更新
{
if(l<=a[k].l&&a[k].r<=r)
{
a[k].sum=x;
return ;
}
pushdown(k);
int mid=(a[k].l+a[k].r)/2;
if(l<=mid) update(l,r,x,2*k);
if(r>mid) update(l,r,x,2*k+1);
a[k].sum=a[k*2].sum+a[k*2+1].sum;
}
int query(int l,int r,int k)
{
if(a[k].l==l&&a[k].r==r) return a[k].sum;
pushdown(k);
int mid=(a[k].l+a[k].r)/2;
if(r<=mid) return query(l,r,2*k);
if(l>mid) return query(l,r,2*k+1);
return query(l,mid,2*k)+query(mid+1,r,2*k+1);
}
int op(int a,int b,int f)//f=1表示加边,为2表示查询路径和
{
if(a==b) return 0;
int ANS=0;
int father=lca(a,b);
ANS=-query(p[father],p[father],1);
while(top[a]!=top[b])
{
if(dep[top[a]]>dep[top[b]]) swap(a,b);
if(f==1) update(p[top[b]],p[b],0,1);
else ANS+=query(p[top[b]],p[b],1);
b=fa[top[b]];
}
if(dep[a]>dep[b]) swap(a,b);
if(f==1)
{
update(p[a],p[b],0,1);
update(p[father],p[father],-ANS,1);
}
else ANS+=query(p[a],p[b],1);
return ANS;
}
int main()
{
int t;
scanf("%d",&t);
int t1=t;
while(t--)
{
init();
scanf("%d%d%d",&n,&m,&q);
int from,to;
for(int i = 1; i <= m; i++){
scanf("%d%d", &from, &to);
if(from > to) swap(from, to);
M1.insert(make_pair(from,to));
}
for(int i = 1; i <= q; i++){
scanf("%d%d%d", &tc[i].op, &tc[i].u, &tc[i].v);
if(tc[i].u > tc[i].v) swap(tc[i].u, tc[i].v);
if(tc[i].op == 1){
multiset<PII>::iterator it = M1.find(make_pair(tc[i].u, tc[i].v));
M1.erase(it);
}
}
for(multiset<PII>::iterator it = M1.begin(); it != M1.end(); it++){
add(it->first, it->second,1);
add(it->second, it->first,1);
}
Tarjan();//O(n)
dfs1(belong[1],belong[1],1);//O(n)
dfs2(belong[1],belong[1]);//O(n)
init_lca();
build(1,c,1);//O(nlogn)
printf("Case #%d:\n",t1-t);
for(int i=q; i>=1; i--)
{
if(tc[i].op==1)//增加边
op(belong[tc[i].u],belong[tc[i].v],1);
else tc[i].ans=op(belong[tc[i].u],belong[tc[i].v],2);
}
for(int i=1; i<=q; i++) if(tc[i].op==2) printf("%d\n",tc[i].ans);
}
return 0;
}