loj10099 点双连通分量

点双连通分量+判断割点。(99是一个神奇的题号值得纪念!!)

void tarjan(int k,int pre){
	dfn[k]=low[k]=++tot; s.push(k); int cnt=0;
	repedge(i,k){
		if ((pre!=-1)&&((i^1)==pre)) continue; //由于无向图建边,这边其实就是pre
		int v=edge[i].to; 
		if (!dfn[v]){ 
		    tarjan(v,i); cnt++; //根节点在dfs树上有多少儿子(到非儿子的边是不算的。。)
		    if ((k==root)&&(cnt>1)) cut[k]=1; 
		    if ((k!=root)&&(low[v]>=dfn[k])) cut[k]=1;
		    if (low[v]>=dfn[k]){
		    	++scc; for(;;){ int x=s.top(); s.pop(); bel[x]=scc; ++sz[scc];
		    	dt[scc].push_back(x); if (x==v) break; }
		    	++sz[scc]; dt[scc].push_back(k);
			}//k是某个点双的割点,可以统计那整个点双了。
			low[k]=min(low[k],low[v]);
		}else low[k]=min(low[k],dfn[v]);
	}
}

然而并不是一个模版了事的啊QAQ。

这道题的解法诡异……几乎和yy没什么区别了,然而菜鸡的我不能理解,于是试图证明。

->yy了很久以后,发现我不会证。

然后我想到了某个黑科技。关于点双,有种神奇的东西叫圆方树。

->以下是鬼畜的yy式证明。

建一棵圆方树,把除了割点以外的圆点都删掉,那么它就没有圆叶子了,而原本所有的割点都是父亲,它仍然是一个完整的树。

对于不只一个方点的树:

1.对于只和一个割点相邻的点双,显然地能且只能在内部放一个。->圆方树的每个方叶子一定放了。

证明:必要性:考虑割点坍塌的情况,此时不能走到别的点双,所以是必要的。

充分性:在所有方叶子都放了的情况下,(割点坍塌的情况处理好了),考虑内部点坍塌的情况。由于点双的性质,还是可以通过割点走到其他方叶子的。

2.对于和多个割点相邻的点双,在圆方树上对它进行观察,一旦一个割点塌了,它一定还是可以走到某个方叶子的。->多个割点的点双不用放。

对于只有一个方点的树:

对于单独的方点,它只能在内部任意选两个点放,不然放的点塌了它就gg了。

证(y)明(y)完了。

#include<bits/stdc++.h>
using namespace std;
#define rep(x,y,z) for (int x=y; x<=z; x++)
#define downrep(x,y,z) for (int x=y; x>=z; x--)
#define ms(x,y,z) memset(x,y,sizeof(z))
#define LL long long
#define repedge(x,y) for (int x=hed[y]; ~x; x=edge[x].nex)
inline int read(){
	int x=0; int w=0; char ch=0;
	while (ch<'0' || ch>'9') w|=ch=='-',ch=getchar();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return w? -x:x;
}
const int N=1005;
const int M=505;
int n,m,nedge,hed[N],dfn[N],low[N],tot,root,cut[N],scc,bel[N],sum[N],cas;
LL sz[N];
stack<int> s;
vector<int> dt[N];
struct Edge{ int to,nex; }edge[M<<1];
void addedge(int a,int b){
	edge[nedge].to=b; edge[nedge].nex=hed[a]; hed[a]=nedge++;
}
void tarjan(int k,int pre){
	dfn[k]=low[k]=++tot; s.push(k); int cnt=0;
	repedge(i,k){
		if ((pre!=-1)&&((i^1)==pre)) continue;
		int v=edge[i].to; 
		if (!dfn[v]){ 
		    tarjan(v,i); cnt++;
		    if ((k==root)&&(cnt>1)) cut[k]=1;
		    if ((k!=root)&&(low[v]>=dfn[k])) cut[k]=1;
		    if (low[v]>=dfn[k]){
		    	++scc; for(;;){ int x=s.top(); s.pop(); bel[x]=scc; ++sz[scc];
		    	dt[scc].push_back(x); if (x==v) break; }
		    	++sz[scc]; dt[scc].push_back(k);
			}
			low[k]=min(low[k],low[v]);
		}else low[k]=min(low[k],dfn[v]);
	}
}
int main(){
	for(;;){
		m=read(); if (!m) break; nedge=0; ms(hed,-1,hed); n=0; ++cas;
		rep(i,1,m){ int a=read(); int b=read(); n=max(n,max(a,b)); addedge(a,b); addedge(b,a); }
		rep(i,1,scc) dt[i].clear(); ms(sz,0,sz); ms(cut,0,cut);
		scc=0; ms(dfn,0,dfn); rep(i,1,n) if (!dfn[i]){ root=i; tarjan(i,-1); }
		//rep(i,1,scc) { rep(j,0,sz[i]-1) cout<<dt[i][j]<<' '; cout<<endl; }
		ms(sum,0,sum); rep(i,1,scc) rep(j,0,sz[i]-1) if (cut[dt[i][j]]) ++sum[i];
		int ans1=0; LL ans2=1;
		rep(i,1,scc) { if (!sum[i]) ans1+=2,ans2*=sz[i]*(sz[i]-1)/2;
		if (sum[i]==1) ans1++,ans2*=(sz[i]-1); }
		printf("Case %d: %d %lld\n",cas,ans1,ans2);
	}
	return 0;
}

然后关于圆方树,(是个好东西啊)有一堆什么在圆方树上树剖的题目,之前颓废了没有打。码住留坑待填。(我怎么这么废啊。。逃。。

 

update:先填坑1/3,裸的建立圆方树的板子。loj10101。

#include<bits/stdc++.h>
using namespace std;
#define rep(x,y,z) for (int x=y; x<=z; x++)
#define downrep(x,y,z) for (int x=y; x>=z; x--)
#define ms(x,y,z) memset(x,y,sizeof(z))
#define LL long long
#define repedge(x,y) for (int x=hed[y]; ~x; x=edge[x].nex)
inline int read(){
	int x=0; int w=0; char ch=0;
	while (ch<'0' || ch>'9') w|=ch=='-',ch=getchar();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return w? -x:x;
}
const int N=205;
const int M=N*N;
const int inf=305;
int n,m,S,T,scc,nedge,tot,cut[N],hed[N],dfn[N],low[N],head[N],Nedge,f[N],root;
struct Edge{ int to,nex; }edge[M<<1],E[M<<1];
stack<int> s;
void addE(int a,int b){
	E[Nedge].to=b; E[Nedge].nex=head[a]; head[a]=Nedge++;
}
void addedge(int a,int b){
	edge[nedge].to=b; edge[nedge].nex=hed[a]; hed[a]=nedge++;
}
void tarjan(int k,int pre){
	dfn[k]=low[k]=++tot; s.push(k); int cnt=0;
	repedge(i,k){
		if ((pre!=-1)&&((i^1)==pre)) continue;
		int v=edge[i].to;
		if (!dfn[v]){ tarjan(v,i); cnt++;
		   if ((k==root)&&(cnt>1)) cut[k]=1;
		   if ((k!=root)&&(low[v]>=dfn[k])) cut[k]=1;
		   if (low[v]>=dfn[k]){
		   	   ++scc; for(;;){ int x=s.top(); s.pop();
		   	   addE(x,n+scc); addE(n+scc,x); if (x==v) break; } 
		   	   addE(k,n+scc); addE(n+scc,k);
		   }  
		   low[k]=min(low[k],low[v]);
		}else low[k]=min(low[k],dfn[v]);
	}
}
#define repE(x,y) for(int x=head[y]; ~x; x=E[x].nex)
int dfs(int k,int fa){
	f[k]=inf; int res=0; if (k==T) return 1;
	repE(i,k){
		int v=E[i].to; if (v==fa) continue;
		int t=dfs(v,k); 
		if (t==1){ res=1; f[k]=min(f[k],f[v]);
		if ((k<=n)&&(k!=S)&&(k!=T)) f[k]=min(f[k],k); }
	}
	return res;
}
int main(){
	n=read(); nedge=0; ms(hed,-1,hed);
	for(;;){ int a=read(); int b=read(); if ((!a)&&(!b)) break; addedge(a,b); addedge(b,a); }
	S=read(); T=read(); Nedge=0; ms(head,-1,head);
	rep(i,1,n) if (!dfn[i]) { root=i; tarjan(i,-1); }
    int ans=dfs(S,S); if ((!ans)||(f[S]>=inf)) puts("No solution"); else printf("%d\n",f[S]);
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值