BZOJ2730_矿场搭建_KEY

题目传送门

这道题用Tarjan求出点-双连通分量,对联通块做一些玄学操作。

细节很多,不说了看code。

code:

/**************************************************************
    Problem: 2730
    User: yekehe
    Language: C++
    Result: Accepted
    Time:0 ms
    Memory:840 kb
****************************************************************/
 
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
 
int read()
{
    char c;while(c=getchar(),c<'0'||c>'9');
    int x=c-'0';while(c=getchar(),c>='0'&&c<='9')x=x*10+c-'0';
    return x;
}
 
int N;
 
struct list{
    int head[505],nxt[505],To[505],cnt;
    void clear(){memset(head,-1,sizeof head);memset(nxt,-1,sizeof nxt);cnt=0;}
     
    void add(int x,int y)
    {
        To[cnt]=y;
        nxt[cnt]=head[x];
        head[x]=cnt;
        cnt++;
    }
}W;
 
int DFN[505],LOW[505],cnt,wif[505];
void tarjan(int now,int fa)
{
    int child=0;
    DFN[now]=LOW[now]=++cnt;
        for(int i=W.head[now];i!=-1;i=W.nxt[i]){
            if(!DFN[W.To[i]]){
                child++;
                tarjan(W.To[i],now);
                LOW[now]=min(LOW[now],LOW[W.To[i]]);
                if(LOW[W.To[i]]>=DFN[now])wif[now]=1;//割顶的判定
            }
            else if(DFN[W.To[i]]<DFN[now]&&W.To[i]!=fa){
                LOW[now]=min(LOW[now],DFN[W.To[i]]);
            }
        }
    if(fa<0&&child==1)wif[now]=0;
}//求点-双连通分量
 
int ct=0;
int vis[505],tong[505];
long long cnc;
void dfs(int now)
{
    cnc++;//求除了割点的联通块内的其他点的数量
    vis[now]=1;//标记该点被访问
        for(int i=W.head[now];i!=-1;i=W.nxt[i]){
            if(!vis[W.To[i]])
                if(wif[W.To[i]])tong[W.To[i]]=ct;//用桶记录割顶
                else dfs(W.To[i]);
        }
}
 
int main()
{
    int TOT=0;
    while((N=read())!=0){
        TOT++;
        W.clear();
        memset(DFN,0,sizeof DFN);
        memset(LOW,0,sizeof LOW);
        memset(tong,0,sizeof tong);
        memset(wif,0,sizeof wif);
        memset(vis,0,sizeof vis);
        cnt=0;
        int TN=0;
            for(int i=1;i<=N;i++){
                int x=read(),y=read();
                TN=max(TN,x);TN=max(TN,y);
                W.add(x,y);W.add(y,x);//双向边
            }
            for(int i=1;i<=TN;i++)
                if(!DFN[i])tarjan(i,-1);
        int ans=0;
        printf("Case %d: ",TOT);
        long long tot=1;//方案数
       for(int i=1;i<=TN;i++)if(!vis[i]&&!wif[i]){//这个点既不是割顶也没被访问过 cnc=0;//联通块内除了割顶的点的数量 dfs(i); int kkk=0;//求有几个割顶 for(int j=1;j<=TN;j++)if(tong[j]==ct)kkk++; if(!kkk){//如果这个联通块内没有割顶,那么要且只要建两个救助站就好了,可以自己画图 ans+=2; tot=tot*cnc*(cnc-1)/2;//用乘法原理和组合求方案数 } else { if(kkk==1){//有一个割顶 ans++; tot=tot*cnc;//乘以联通块内除了割顶的点的数量 } }//当割顶的数量大于等于2时不能也不需要建救助站。不能是因为要求数量最少,可以自己画图 } printf("%d %lld\n",ans,tot); } }

 

posted @ 2018-03-06 17:44  Cptraser  阅读(172)  评论(0编辑  收藏  举报