HDU6431 NewNippori
NewNippori
给定 \(n\) 个点 \(m\) 条边的无向图,求
\(\sum n ≤ 4 × 10^5,\sum m ≤ 7 × 10^5\)。
题解
最大流显然要转化成最小割来考虑。
2018 Multi-University Training Contest 10 solutions BY 雅礼中学
首先题目就是要你求每对点之间的最大流对 \(3\) 取 \(\min\) 的和。也就是要判断
-
有多少对点满足存在一种方案,删去一条边后不连通。
-
有多少对点满足存在一种方案,删去两条边后不连通。
第一种很好算,找出边双连通分量即可。不妨假设现在整张图是边双连通的。
先搞出DFS生成树来, 容易发现删去的两条边可能是:
-
某一条边是树边,另一条边是非树边:要求这条树边仅被这条非树边覆盖
-
两条边都是树边:要求这两条树边被覆盖情况相同。不妨考虑类似 共价大爷游长沙 的思路,即给每条非树边随机一个 \(10^{18}\) 以内的权值,一个树边的权值为覆盖它的非树边的权值的异或和。然后用上
map
就很好统计了,时间复杂度 \(O(n \log m)\)。
是的,好统计。口胡谁TM都会。
做出生成树之后怎么办呢?考虑再进行一次“边三连通分量”划分,把相互之间最小割 \(\geq 3\) 的分在同一个连通块中。
-
树边+非树边就能割开的组合:把生成树上的这条树边断掉。
-
都是树边的组合:有些复杂。因为无向图的DFS树只有返祖边,所以这样的树边一定是连续的一段。
考虑 \(x \leftrightarrow y \leftrightarrow z \leftrightarrow w\) 这一条链,其中 \((x,y),(y,z),(z,w)\) 被覆盖的情况相同,并且非树边数量要 \(\geq 2\),不然就是上面那个树边+非树边组合了。
在这种情况下,\(y,z\) 子树中的非树边一定逃不出它们的子树内部,那么 \(y,z\) 子树显然可以各自形成一个边三连通块,直接把 \((x,y),(y,z),(z,w)\) 这三条边断开就行了。
但是 \(x,w\) 子树内的点却可能形成 \(\geq 3\) 的最小割,这两棵子树可能在同一边三连通分量中。怎么办呢?再在 \(x,w\) 之间连一条边就行了。
为了做这题,我又写了对拍和造数据。
IN int64 rd(){
return rand()^rand()<<15^(int64)rand()<<30^(int64)rand()<<45^(int64)rand()<<60;
}
CO int N=1e5+10,M=3e5+10;
struct edge {int v,next;}e[2*M];
int head[N],tot;
IN void link(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
e[++tot]=(edge){u,head[v]},head[v]=tot;
}
int pos[N],low[N],dfn;
int stk[N],top;
int col[N],idx;
vector<int> ecc[N];
void tarjan(int u,int ine){
pos[u]=low[u]=++dfn;
stk[++top]=u;
for(int i=head[u];i;i=e[i].next)if(i!=(ine^1)){
if(!pos[e[i].v]){
tarjan(e[i].v,i);
low[u]=min(low[u],low[e[i].v]);
}
else low[u]=min(low[u],pos[e[i].v]);
}
if(low[u]==pos[u]){
ecc[++idx].clear();
do{
int x=stk[top];
col[x]=idx,ecc[idx].push_back(x);
}while(stk[top--]!=u);
}
}
namespace T{
int n,ref[N];
edge e[4*M];
int head[N],tot;
IN void init(){
fill(head+1,head+n+1,0),tot=1;
}
IN void link(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
e[++tot]=(edge){u,head[v]},head[v]=tot;
}
int pos[N],dfn;
int64 cnt[N];
map<int64,int> tree;
set<int64> non;
map<int64,pair<int,int> > add;
int tag[2*M];
void build(int u,int ine){
pos[u]=++dfn,cnt[u]=0;
for(int i=head[u];i;i=e[i].next)if(i!=(ine^1)){
if(!pos[e[i].v]){
tag[i/2]=1;
// cerr<<"tree "<<i/2<<endl;
build(e[i].v,i);
cnt[u]^=cnt[e[i].v];
}
else if(pos[e[i].v]<pos[u]){
int64 x=rd();
// cerr<<"rand "<<i/2<<" "<<x<<endl;
cnt[u]^=x,cnt[e[i].v]^=x;
non.insert(x);
}
}
// cerr<<"rand "<<ine/2<<" "<<cnt[u]<<endl;
if(non.count(cnt[u])){
tag[ine/2]=0;
// cerr<<"non "<<ine/2<<endl;
return; // edit 1
}
if(tree.count(cnt[u])){
tag[ine/2]=0,tag[tree[cnt[u]]/2]=0;
// cerr<<"tr "<<ine/2<<" "<<tree[cnt[u]]/2<<endl;
}
tree[cnt[u]]=ine;
if(!add.count(cnt[u])) add[cnt[u]]=make_pair(u,0);
else add[cnt[u]].second=e[ine^1].v;
}
int vis[N];
int64 main(){
// cerr<<"edge m="<<tot/2<<endl;
// for(int i=1;i<=tot/2;++i){
// int u=e[2*i].v,v=e[2*i+1].v;
// cerr<<i<<" "<<u<<" "<<v<<endl;
// }
non.clear(),tree.clear();
add.clear();
fill(pos+1,pos+n+1,0),dfn=0;
fill(tag+1,tag+tot+1,0);
build(1,0);
for(map<int64,pair<int,int> >::iterator i=add.begin();i!=add.end();++i)
if(i->second.first and i->second.second){
link(i->second.first,i->second.second);
// cerr<<"link "<<i->second.first<<" "<<i->second.second<<endl;
tag[tot/2]=1;
}
// cerr<<"tag="<<endl;
// for(int i=1;i<=tot/2;++i) cerr<<i<<" tag="<<tag[i]<<endl;
// cerr<<endl;
fill(vis+1,vis+n+1,0);
int64 ans=0;
for(int i=1;i<=n;++i)if(!vis[i]){
int siz=0;
deque<int> Q(1,i);
while(Q.size()){
int u=Q.front();Q.pop_front();
vis[u]=1,++siz;
for(int i=head[u];i;i=e[i].next)if(tag[i/2])
if(!vis[e[i].v]) Q.push_back(e[i].v);
}
ans+=(int64)siz*(siz-1)/2;
}
// cerr<<"ans="<<3*ans+2*((int64)n*(n-1)/2-ans)<<endl;
return 3*ans+2*((int64)n*(n-1)/2-ans);
}
}
int vis[2*M];
int64 solve(int x){
idx=0,tarjan(x,0);
int64 ans=0;
int n=0;
for(int i=1;i<=idx;++i) n+=ecc[i].size();
for(int i=1;i<=idx;++i) ans+=(int64)ecc[i].size()*(n-ecc[i].size());
ans/=2;
for(int i=1;i<=idx;++i){
// reverse(ecc[i].begin(),ecc[i].end());
// cerr<<"ecc=";
// for(int j=0;j<(int)ecc[i].size();++j) cerr<<" "<<ecc[i][j];
// cerr<<endl;
T::n=0;
for(int j=0;j<(int)ecc[i].size();++j) T::ref[ecc[i][j]]=++T::n;
T::init();
for(int j=0;j<(int)ecc[i].size();++j){
int u=ecc[i][j];
for(int k=head[u];k;k=e[k].next)if(!vis[k]){
int v=e[k].v;
if(col[v]!=i) continue;
T::link(T::ref[u],T::ref[v]);
vis[k]=vis[k^1]=1;
}
}
ans+=T::main();
}
return ans;
}
void real_main(){
int n=read<int>(),m=read<int>();
fill(head+1,head+n+1,0),tot=1;
for(int i=1;i<=m;++i) link(read<int>(),read<int>());
fill(pos+1,pos+n+1,0),dfn=0;
fill(vis+1,vis+2*m+1,0);
int64 ans=0;
for(int i=1;i<=n;++i)if(!pos[i]) ans+=solve(i);
printf("%lld\n",ans);
}
int main(){
// freopen("gen.in","r",stdin),freopen("mine.out","w",stdout);
srand(20030506);
for(int T=read<int>();T--;) real_main();
return 0;
}