1. 程式人生 > >【Tarjan】HDU

【Tarjan】HDU

題意:

在一張有向圖上加儘可能多的邊,使它不成為強連通圖。輸出能加的邊的數量。

題解:

首先Tarjan縮點。考慮把圖上的點分成兩塊,左邊點的數量為n,右邊點的數量為mn+m=tt為點的總數。

左邊所有的點互相連線,一共有n*(n-1)條邊;右邊所有的點互相連線,一共有m*(m-1)條邊。

左邊的點與右邊連一條有向邊,一共有n*m條邊。

所以總共能夠構成n*(n-1)+m*(m-1)+n*m條邊。

拆開化簡就是t^{2}-t-n*m。要使這個值最大,就要是n*m儘可能地小。因為n+m=t,所以n*m最小的情況就是兩者差值最大的情況。

所以我們可以找出所有入度為零或出度為零的點,找一個最小值代入為n。將最後構造出來的邊數減去原來存在的邊即是答案。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e5+7;
struct Edge{
    ll v,nxt;
    Edge(ll v=0,ll nxt=0):v(v),nxt(nxt){}
}e[N];
int in[N],ou[N];
ll t,n,m,u,v,cs;

ll p[N],edn;
void add(ll u,ll v){
    e[++edn]=Edge(v,p[u]);p[u]=edn;
}
ll dfn[N],low[N],num[N],st[N],scc[N],sccnum,idx,top;
void tarjan(ll u){
    dfn[u]=low[u]=++idx;
    st[++top]=u;
    for(ll i=p[u];~i;i=e[i].nxt){
        ll v=e[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!scc[v]) low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
        sccnum++;
        while(1){
            ll x=st[top--];
            num[sccnum]++;
            scc[x]=sccnum;
            if(x==u) break;
        }
    }
}

void init(){
    memset(p,-1,sizeof(p));edn=-1;
    memset(in,0,sizeof(in));
    memset(ou,0,sizeof(ou));
    memset(num,0,sizeof(num));
    memset(dfn,0,sizeof(dfn));
    memset(scc,0,sizeof(scc));
    sccnum=idx=top=0;
}

void rebuild(){
    for(int x=1;x<=n;x++){
        int u=scc[x];
        for(int i=p[x];~i;i=e[i].nxt){
            int v=scc[e[i].v];
            if(u==v) continue;
            ou[u]++,in[v]++;
        }
    }
}
int main(){
    scanf("%lld",&t);
    while(t--){
        init();
        scanf("%lld%lld",&n,&m);
        for(ll i=1;i<=m;i++){
            scanf("%lld%lld",&u,&v);
            add(u,v);
        }
        for(ll i=1;i<=n;i++)
            if(!scc[i]) tarjan(i);
        if(sccnum==1) printf("Case %lld: -1\n",++cs);
        else{
            rebuild();
            memset(dp,0,sizeof(dp));
            ll tmp=n;
            for(ll i=1;i<=sccnum;i++){
                if(in[i]==0||ou[i]==0){
                    tmp=min(tmp,num[i]);
                }
            }
            ll ans=n*n-n-tmp*(n-tmp);
            printf("Case %lld: %lld\n",++cs,ans-m);
        }
    }
}