今天我这一台机器好像不知道怎么回事,好像被限速了似的。贼慢
今天晚上还有可能回去上万恶的文化课
我好郁闷呀。
不过今天也终于将点双和边双彻底理解了
%%% tarjan
题目入口
一开始没有考虑割点能存在于不同的点双中。
考虑到了以后又在纠结如何计数233
结果发现竟然是最简单的乘法原理233
割点数量 | (0) | (1) | (>=2) |
---|---|---|---|
方案数 | (C^2_n) | (n-1) | $$0$$ |
什么意思呢,当一个点双中没有割点时,那么放哪都可以,不过要放两个。如果只放一个的话,如果碰巧就是这个塌了。那就GG了
当点双中只有一个割点时,那么放在这个点双的任意一个位置就行了。如果割点被GG了。我们有在点双中的逃生出口。如果点双中的逃生出口GG了。我们就可以通过割点溜之大吉。
如果有两个及以上的割点。因为只会塌方一个,所以无论怎么塌,都可以转移到其他的点双中。就不用再建立了。
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
int m,n;
int dfn[510],low[510];
int g_num;
vector<int>g[510];
vector<int>l[510];
int t,root;
bool p[510];
int stack[510],top;
void tarjan(int now)
{
dfn[now]=low[now]=++t;
stack[++top]=now;
int chi=0;
for(int i=0;i<l[now].size();i++)
{
int nxt=l[now][i];
if(!dfn[nxt])
{
chi+=1;
tarjan(nxt);
low[now]=min(low[now],low[nxt]);
if((now==root&&chi>1)||(now!=root&&dfn[now]<=low[nxt]))
p[now]=true;
if(low[nxt]>=dfn[now])
{
int pas;
g_num+=1;
do
{
pas=stack[top--];
g[g_num].push_back(pas);
}while(pas!=nxt);
g[g_num].push_back(now);
}
}
else low[now]=min(dfn[nxt],low[now]);
}
}
void solve()
{
for(int i=1;i<=n;i++)
g[i].clear(),p[i]=false,l[i].clear(),dfn[i]=0,low[i]=0;
n=0;
t=0;g_num=0;
int a,b;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&a,&b);
l[b].push_back(a);
l[a].push_back(b);
a=max(a,b);
n=max(a,n);
}
long long set=0,way=1;
for(int i=1;i<=n;i++)
if(!dfn[i])
root=i,tarjan(i);
for(int i=1;i<=g_num;i++)
{
int cut=0;
for(int j=0;j<g[i].size();j++)
if(p[g[i][j]]) cut+=1;
int tot=g[i].size();
if(!cut) set+=2ll,way*=1ll*(tot-1)*tot/2;
if(cut==1) set+=1ll,way*=1ll*(tot-1);
}
//printf("%lld %lld
",set,way);
cout<<set<<" "<<way<<endl;
}
int main()
{
freopen("input.in","r",stdin);
freopen("input.out","w",stdout);
scanf("%d",&m);
int tot=0;
while(m)
{
printf("Case %d: ",++tot);
solve();
scanf("%d",&m);
}
}