hdu 5458 Stability(生成树,树链剖分,好题)
Stability
Time Limit: 3000/2000 MS (Java/Others) Memory Limit: 65535/102400 K (Java/Others)
Total Submission(s): 1607 Accepted Submission(s): 396
Problem Description
Given an undirected connected graph G with n nodes and m edges, with possibly repeated edges and/or loops. The stability of connectedness between node u and node v is defined by the number of edges in this graph which determines the connectedness between them (once we delete this edge, node u and v would be disconnected).
You need to maintain the graph G, support the deletions of edges (though we guarantee the graph would always be connected), and answer the query of stability for two given nodes.
Input
There are multiple test cases(no more than 3 cases), and the first line contains an integer t, meaning the totally number of test cases.
For each test case, the first line contains three integers n, m and q, where 1≤n≤3×104,1≤m≤105 and 1≤q≤105. The nodes in graph G are labelled from 1 to n.
Each of the following m lines contains two integers u and v describing an undirected edge between node u and node v.
Following q lines - each line describes an operation or a query in the formats:
⋅ 1 a b: delete one edge between a and b. We guarantee the existence of such edge.
⋅ 2 a b: query the stability between a and b.
Output
For each test case, you should print first the identifier of the test case.
Then for each query, print one line containing the stability between corresponding pair of nodes.
Sample Input
1
10 12 14
1 2
1 3
2 4
2 5
3 6
4 7
4 8
5 8
6 10
7 9
8 9
8 10
2 7 9
2 7 10
2 10 6
2 10 5
1 10 6
2 10 1
2 10 6
2 3 10
1 8 5
2 5 10
2 4 5
1 7 9
2 7 9
2 10 5
Sample Output
Case #1:
0
0
0
0
2
4
3
3
2
3
4
Source
2015 ACM/ICPC Asia Regional Shenyang Online
题意:
给出一个 \(n\) 个结点 \(m\) 条边的无向图(保证连通),可能有重边和自环,现在给出 \(q\) 组询问:
- $ 1,u,v $ 删除 \(u,v\) 之间的一条边。
- \(2,u,v\) 询问结点 \(u\) 到结点 \(v\) 的路径上有多少关键边,关键边就是指 \(u\) 到 \(v\) 的所有路径必须经过的边
保证在删边过程中图一直连通。
题解:
可以逆着来,首先得到所有操作完后的图,对这个图求一棵生成树,然后将生成树上每一条边的权值置为1,再树链剖分,然后一条条加边(此时必定会形成环),对于加的边 \((u,v)\),将 \(u->v\) 路径上的边置为1,对于询问 \((u,v)\) ,答案为 \(u->v\) 的边上权值之和。
代码里使用了claris求三元环那个代码里面hash加边法。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<stack>
#include<set>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define fi first
#define se second
#define dbg(...) cerr<<"["<<#__VA_ARGS__":"<<(__VA_ARGS__)<<"]"<<endl;
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=3e4+100;
const int maxm=1e5+100;
const int Base=(1<<20)-1;
int head[maxn];
struct edge
{
int to,next;
}e[maxn*2];
int tol=0;
void add(int u,int v)
{
e[++tol].to=v,e[tol].next=head[u],head[u]=tol;
}
int G[Base+1];
struct edge1
{
int from,to,id,next;
}e1[maxm];
int tol1=0;
void add1(int u,int v,int id)
{
int h=((u<<8)|v)&Base;
e1[++tol1].to=v,e1[tol1].from=u,e1[tol1].id=id,e1[tol1].next=G[h],G[h]=tol1;
}
pair<pair<int,int>,int> e2[maxm];
struct que
{
int u,v,op;
}query[maxm];
int check(int u,int v)
{
int h=((u<<8)|v)&Base;
for(int i=G[h];i;i=e1[i].next)
{
int u1=e1[i].from,v1=e1[i].to;
if(u1==u&&v1==v&&e2[e1[i].id].se==1)
{
return e1[i].id;
}
}
return 0;
}
int pa[maxn];
int find(int x)
{
return pa[x]==x? x:pa[x]=find(pa[x]);
}
set<int> st;
int seg[maxn*4],lazy[maxn*4];
void build(int i,int l,int r)
{
lazy[i]=0;
if(l==r)
{
seg[i]=1;
return;
}
int m=(l+r)/2;
build(i*2,l,m);
build(i*2+1,m+1,r);
seg[i]=seg[i*2]+seg[i*2+1];
}
void update(int i,int l,int r,int L,int R)
{
if(l==L&&r==R)
{
seg[i]=0;
lazy[i]=1;
return;
}
if(lazy[i]) return;
int m=(L+R)/2;
if(r<=m) update(i*2,l,r,L,m);
else if(l>m) update(i*2+1,l,r,m+1,R);
else
{
update(i*2,l,m,L,m);
update(i*2+1,m+1,r,m+1,R);
}
seg[i]=seg[i*2]+seg[i*2+1];
}
int ask(int i,int l,int r,int L,int R)
{
if(l==L&&r==R)
{
return seg[i];
}
if(lazy[i]) return 0;
int m=(L+R)/2;
if(r<=m) return ask(i*2,l,r,L,m);
else if(l>m) return ask(i*2+1,l,r,m+1,R);
else
{
return ask(i*2,l,m,L,m)+ask(i*2+1,m+1,r,m+1,R);
}
}
int pos;//线段树总区间大小
int fa[maxn],top[maxn],son[maxn],num[maxn],p[maxn],dep[maxn];
//top[v]表示v所在的重链的顶端节点,对于u的轻儿子v有top[v]=v,fa[v]表示v的父亲节点,num[v]表示以v为根的子树的节点数,p[v]表示v与其父亲节点的连边在线段树中的位置,son[v]为v的重儿子,dep为深度
void dfs(int u,int f,int d)
{
num[u]=1;
fa[u]=f;
dep[u]=d;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(v==f) continue;
dfs(v,u,d+1);
if(son[u]==0||num[v]>num[son[u]])
son[u]=v;
num[u]+=num[v];
}
}
void dfs2(int u,int sp)
{
top[u]=sp;
if(son[u])
{
p[u]=pos++;
dfs2(son[u],sp);
}
else //叶子结点
{
p[u]=pos++;
return;
}
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(v!=son[u]&&v!=fa[u])
dfs2(v,v);
}
}
void init(int n)
{
pos=1;
rep(i,1,n+1) fa[i]=son[i]=0;
}
int solve(int u,int v,int n) //查询u-v之间的最大边
{
int f1=top[u],f2=top[v];
int ans=0;
while(f1!=f2)
{
if(dep[f1]<dep[f2])
{
swap(f1,f2);
swap(u,v);
}
ans+=ask(1,p[f1],p[u],1,n);
u=fa[f1],f1=top[u];
}
if(u==v) return ans; //注意此处的操作对象是树上的边,当是点的时候此行应该注释掉!
if(dep[u]>dep[v]) swap(u,v);
return ans+ask(1,p[son[u]],p[v],1,n);
}
void solve1(int u,int v,int n) //查询u-v之间的最大边
{
int f1=top[u],f2=top[v];
while(f1!=f2)
{
if(dep[f1]<dep[f2])
{
swap(f1,f2);
swap(u,v);
}
update(1,p[f1],p[u],1,n);
u=fa[f1],f1=top[u];
}
if(u==v) return; //注意此处的操作对象是树上的边,当是点的时候此行应该注释掉!
if(dep[u]>dep[v]) swap(u,v);
update(1,p[son[u]],p[v],1,n);
}
int ans[maxm];
int main()
{
int cas;
scanf("%d",&cas);
int t=0;
while(cas--)
{
st.clear();
tol=tol1=0;
int n,m,q;
scanf("%d%d%d",&n,&m,&q);
init(n);
rep(i,1,n+1) head[i]=0,pa[i]=i;
rep(i,0,Base+1) G[i]=0;
rep(i,1,m+1)
{
int u,v;
scanf("%d%d",&u,&v);
if(u>v) swap(u,v);
add1(u,v,i);
e2[i]=make_pair(make_pair(u,v),1);
}
rep(i,1,q+1)
{
scanf("%d%d%d",&query[i].op,&query[i].u,&query[i].v);
if(query[i].u>query[i].v) swap(query[i].u,query[i].v);
if(query[i].op==1)
{
int id=check(query[i].u,query[i].v);
e2[id].se=0;
}
}
rep(i,1,m+1)
{
if(e2[i].se)
{
int u=e2[i].fi.fi,v=e2[i].fi.se;
int fu=find(u),fv=find(v);
if(fu!=fv)
{
pa[fu]=fv;
add(u,v);
}
else
{
st.insert(i);
}
}
}
dfs(1,0,1);
dfs2(1,1);
build(1,1,n);
for(auto item:st)
{
int u=e2[item].fi.fi,v=e2[item].fi.se;
solve1(u,v,n);
}
per(i,1,q+1)
{
if(query[i].op==1)
{
solve1(query[i].u,query[i].v,n);
}
else if(query[i].op==2)
{
ans[i]=solve(query[i].u,query[i].v,n);
}
}
printf("Case #%d:\n",++t);
rep(i,1,q+1) if(query[i].op==2) printf("%d\n",ans[i]);
}
return 0;
}