【2017年浙江工业大学大学生程序设计迎新赛决赛】 A 小杰接网线【思维+组合数学】

时间限制:C/C++ 5秒,其他语言10秒
空间限制:C/C++ 131072K,其他语言262144K
64bit IO Format: %lld
题目描述
小杰是学院中公认的计算机大神,有一天小杰受邀去机房帮忙接网线,他三下五除二地就帮忙完成了。可是善于思考地他仍意有未尽,他觉得这次的线路有许多奇妙的性质,故来请教你。
机房有n台不同的主机(编号为0,1,2,⋯,n−1),共连有n条网线,每台主机恰连着两条网线,通向其他的主机。相互之间有网线相连的主机可以通信,主机可以转发其他主机的信息,换句话说,如果主机A和主机C可以通信,主机B和主机C可以通信,那么主机A和主机B可以通信。现在想在一些主机间添加一些网线,使得任意一个主机故障时,其他的主机之间仍能互相通信(注:在一个主机故障时,所有与该主机直接相连的线路将无法使用)。那么问题来了,在添加的网线最少时,请问有多少种连线的方案,由于连线方案可能非常多,请将方案数对1000,000,007取模。
输入描述:
第一行表示主机数n。
接下来有n行,第i(i=0,1,2,⋯,n−1)行有两个数a,b,表示主机i和主机a,主机i和主机b各有网线相连。
输入有多组数据,n=0时,表示输入结束,请不要有对该组数据任何输出。
输出描述:
每组数据输出一行,表示方案数对1000,000,007取模的结果。(行末不要有多余的空白字符)
示例1
输入

5
2 3
4 4
0 3
0 2
1 1
12
11 11
10 10
9 9
8 8
7 7
6 6
5 5
4 4
3 3
2 2
1 1
0 0
2
1 1
0 0
0
输出

6
3840
0
备注:
2≤n≤3×105,
不超过10组数据 n≥2×105,
不超过20组数据 n≥104,
总数据组数不超过200

分析:
1 : 题目的输入非常重要,如果发现不了这点,完全没有办法写。就是对于每个点有且只有两个边,那么说明这个图是由许多的简单的圆环(简单的圆环:在一个圆周上选择n个位置变成点 ,然后就构成了一个n个点的简单环)构成的。然后我们就可以把每个简单环都当做一个新的点,来继续处理问题。
2 现在我们有 缩点后的n个独立的点, 原来的问题:去掉任意的点,图仍然为连通图, 问题转化: 可以想一下简答的问题,对于两个新点来说,怎么保证 去掉任意的老点仍然可以为连通的,只需要分别从两个环中找到两个点,随意相互组合就可以做到。推广到n个新点也是同样,一个新点需要对左右的两个新点做上述的操作即可。
3 n个新点 最后进行排列的时候线性排布就可以,但是种类不同的只取决于相对位置的不同,所以可以考虑圆周排布 (但是头尾不想接的那种)。 另外注意:我们只是将每个新点中,可能连边的数对分出来假如为cnt[i],当连边的时候,对于两个新点之间连边时候,应该为2*cnt[1]*cnt[2] , 【这一部分说的模糊,难说清楚,有前两个部分理解,自己想清楚最后的计数应该不难吧】

代码

#include<bits/stdc++.h>
using namespace std;
#define LL  long long

const int MAXN = 3e5+11 ;
const int MAXM = 1e6;
const int mod = 1e9+7;

LL k[MAXN+11],s[MAXN+11];
void Init(){
    k[0]=1;s[0]=1;
    for(LL i=1;i<=MAXN;i++){
        k[i]=k[i-1]*i; k[i]%=mod;
        s[i]=s[i-1]*2; s[i]%=mod;
    }
}
vector<int>ve[MAXN+11];
void init(int n){
    for(int i=0;i<=n;i++)
        ve[i].clear();
}
void getmap(int m){
    for(int i=1;i<=m;i++){
        int x,y;scanf("%d%d",&x,&y); x++;y++;
        ve[i].push_back(x);
        ve[i].push_back(y);
    }
}
LL cnt; bool vis[MAXN+1];
void dfs(int now,int pre){
    cnt++; vis[now]=true;
    for(int i=0;i<ve[now].size();i++){
        int v=ve[now][i];
        if(vis[v]) continue;
        dfs(v,now);
    }
}

int main(){
    Init(); int n;
    while(scanf("%d",&n)&&n){
        init(n);  getmap(n);
        memset(vis,false,sizeof(vis));
        int nn=0; LL ans=1;
        for(int i=1;i<=n;i++){
            if(!vis[i]){
                nn++;
                cnt=0; dfs(i,-1);// cnt为每个新点中包含的老点数目
                ans*=(cnt-1)*cnt/2%mod; ans%=mod;
            }
        }
        if(nn==1) { puts("0"); continue; }
        ans*=k[nn-1]; ans%=mod;// n个不同点的圆周排列方案数为(n-1)!
        ans*=s[nn-1]; ans%=mod;// n-1个双倍
        printf("%lld\n",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值