1. 程式人生 > >POJ 2987 最大權閉合圖

POJ 2987 最大權閉合圖

//poj2987
//最大權閉合圖加最少點數
//最少點數我就先忽視了
莫名奇妙的過了。。
總之題目不簡單,但資料很水。。


請參考胡伯濤的論文《最小割模型在資訊學競賽中的應用》

閉合圖的概念就很好引出了。在一個圖中,我們選取一些點構成集合,記為V,且集合中的出邊(即集合中的點的向外連出的弧),所指向的終點(弧頭)也在V中,則我們稱V為閉合圖。最大權閉合圖即在所有閉合圖中,集合中點的權值之和最大的V,我們稱V為最大權閉合圖。


首先引入結論,最小割所產生的兩個集合中,其源點S所在集合(除去S)為最大權閉合圖,接下來我們來說明一些結論。

證明:最小割為簡單割。

        引入一下簡單割的概念:割集的每條邊都與S或T關聯。(請下面閱讀時一定分清最小割與簡單割,容易混淆)

        那麼為什麼最小割是簡單割呢?因為除S和T之外的點間的邊的容量是正無窮,最小割的容量不可能為正無窮。所以,得證。

證明網路中的簡單割與原圖中閉合圖存在一一對應的關係。(即所有閉合圖都是簡單割,簡單割也必定是一個閉合圖)。

        證明閉合圖是簡單割:如果閉合圖不是簡單割(反證法)。那麼說明有一條邊是容量為正無窮的邊,則說明閉合圖中有一條出邊的終點不在閉合圖中,矛盾。

        證明簡單割是閉合圖:因為簡單割不含正無窮的邊,所以不含有連向另一個集合(除T)的點,所以其出邊的終點都在簡單割中,滿足閉合圖定義。得正。

證明最小割所產生的兩個集合中,其源點S所在集合(除去S)為最大權閉合圖。

        首先我們記一個簡單割的容量為C,且S所在集合為N,T所在集合為M。

        則C=M中所有權值為正的點的權值(即S與M中點相連的邊的容量)+N中所有權值為負的點權值的絕對值(即N中點與T中點相連邊的容量)。記(C=x1+y1);(很好理解,不理解畫一個圖或想象一下就明白了)。

        我們記N這個閉合圖的權值和為W。

        則W=N中權值為正的點的權值-N中權值為負的點的權值的絕對值。記(W=x2-y2);

        則W+C=x1+y1+x2-y2。

        因為明顯y1=y2,所以W+C=x1+x2;

        x1為M中所有權值為正的點的權值,x2為N中權值為正的點的權值。

        所以x1+x2=所有權值為正的點的權值之和(記為TOT).

        所以我們得到W+C=TOT.整理一下W=TOT-C.

        到這裡我們就得到了閉合圖的權值與簡單割的容量的關係。

        因為TOT為定值,所以我們欲使W最大,即C最小,即此時這個簡單割為最小割,此時閉合圖為其源點S所在集合(除去S)。得正。


至此,我們就將最大權閉合圖問題轉化為了求最小割的問題。求最小割用最小割容量=最大流,即可將問題轉化為求最大流的問題。

-----------------------------------------------------------

然後再說本題

本題還有一個要求就是要求不僅要是最大權,並且要求點數還最少

看一個神牛的證明----http://hi.baidu.com/dispossessed/blog/item/2396c0ddbc73a2caa044df44.html


下面證明最小割對應取點方案就是最小取點數

由於原圖是個DAG圖,所以對於取得的最大權閉合圖K,取它的任意一個子圖G,如果從K-G仍然是一個閉合圖,那麼的點權和一定大於等於0,例如:1->2,2->3,1->4,4->5,若最大權閉合圖為:{1,2,3,4,5},那麼其中任一滿足條件的G({1},{1,2},{1,2,3},{1,4},{1,4,5})點權和一定大於等於0,否則去除G,K-G仍然為閉合圖,但是K-G的點權和會大於K

所以如果有兩種取點方式使得權值相同,但是取點數不同的話,那麼肯定存在一個可以移除的滿足條件的子圖G,其點權和為0

下面考慮構造的網路,對於G在網路中的對應圖G',由於在網路求的是最小割,即最大流,而且G的點權和為0,所以G'中與源點S連邊的容量和等於G'中與匯點T連邊的流量和,同時由於去除G後K還是一個閉合圖,所以只有可能G'中的流量流入K'-G',不可能有流量從K'-G'流入G',所以G'的邊中除了流量為inf的那些,一定是滿流的

再考慮在殘留網路中求出取點集的方法,從源點開始floodfill,忽略滿流邊,即殘留網路中的0流邊,可以遍歷到的點就是要取的點集了,這個道理想一下簡單割和閉合圖的取法一一對應就可以了

那麼G'既然是滿流的,在殘留網路中就不可能對這些0流邊進行處理,那就不會取到G中的點進入取點集,所以建立網路求得得最小割對應的取法取出的就是最小的點數了

--------------------------------

當然還有一種是神奇的放大邊權方法

建圖前,對所有b[i],執行變換b[i]=b[i]*10000-1,然後,會驚異地發現,
此時最大流所對應的方案就是滿足辭退最少人數的了。
為什麼?顯然,變換後的流量r2除以10000後再取整就等於原來的流量,但是
r2的後四位卻蘊含了辭退人數的資訊:每多辭退一個人,流量就會少1。
想法還是又妙又多的,要搞清晰
首先他的最小割是唯一的,然後他證明了如何在這個最小割裡找一個最少的點數。
比如如果後四位是9998,則裁的為2個人。
#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<vector>
#include<queue>
#define ll __int64
#define maxn 6000
#define INF 0x3f3f3f3f
using namespace std;
ll n,m;
ll tempa,tempb;
ll w[maxn];
ll tot;
bool visit[maxn];
struct Dinic
{
    ll edgeNum,source,sink;
    bool vis[maxn];
    ll d[maxn];
    ll cur[maxn];
    ll mark[maxn];
    struct edge
	{
        ll from,to,cap,flow;
    };
    vector<edge> edges;
    vector<ll> mapt[maxn];
    void addEdge(ll from,ll to,ll cap){
        //pf("%d %d %d\n",from,to,cap);
        edges.push_back((edge){from,to,cap,0});
        edges.push_back((edge){to,from,0,0});
        edgeNum = edges.size();
        mapt[from].push_back(edgeNum-2);
        mapt[to].push_back(edgeNum-1);
    }
    bool bfs()
	{
        memset(vis,0,sizeof vis);
        queue<ll> q;
        q.push(source);
        d[source] =0; vis[source] =1;
        while(!q.empty()){
            ll x = q.front(); q.pop();
            for(ll i=0;i<mapt[x].size();i++){
                edge& e = edges[mapt[x][i]];
                if(!vis[e.to]&&e.cap>e.flow){
                    vis[e.to] = 1;
                    d[e.to] = d[x] +1;
                    q.push(e.to);
                }
            }
        }
        return vis[sink];
    }
    ll dfs(ll x,ll a){
        if(x==sink||a==0) return a;
        ll flow=0,f;
        for(ll& i=cur[x];i<mapt[x].size();i++){
            edge& e = edges[mapt[x][i]];
            if(d[x]+1==d[e.to]&&(f=dfs(e.to,min(a,e.cap-e.flow)))>0){
                e.flow += f;
                edges[mapt[x][i]^1].flow -= f;
                flow += f;
                a -= f;
                if(a==0) break;
            }
        }
        return flow;
    }
    ll maxFlow(ll source,ll sink){
        this->sink = sink; this->source = source;
        ll flow  = 0;
        while(bfs()){
            memset(cur,0,sizeof cur);
            flow += dfs(source,INF);
        }
        return flow;
    }
     void  dfsans(ll x)
     {
         visit[x]=true;
         for(ll i=0;i<mapt[x].size();i++)
         {
            edge& e = edges[mapt[x][i]];
            if((e.cap-e.flow)>0)
            {
               ll temp=e.to;
               if(!visit[temp])
               {
               dfsans(temp);
                tot++;
               }
            }
         }
     }

};

int main()
{
    while(scanf("%I64d%I64d",&n,&m)!=EOF)
    {
        Dinic dinic;
        ll ans=0;
        ll s=0;ll dest=n+1;
        for(ll i=1;i<=n;i++)
        {
            scanf("%I64d",&w[i]);
            if(w[i]>0)
            {
            dinic.addEdge(s,i,w[i]);
            ans+=w[i];
            }
            else dinic.addEdge(i,dest,-w[i]);
        }
        for(ll i=1;i<=m;i++)
        {
           scanf("%I64d%I64d",&tempa,&tempb);
           dinic.addEdge(tempa,tempb,INF);
        }
        ans=ans-dinic.maxFlow(s,dest);
        memset(visit,false,sizeof(visit));
        tot=0;
        dinic.dfsans(s);
        cout<<tot<<" "<<ans<<endl;
    }

}