1. 程式人生 > >HDU 1853 & HDU 3488【有向環最小權值覆蓋問題 】最小費用最大流

HDU 1853 & HDU 3488【有向環最小權值覆蓋問題 】最小費用最大流

題意:

給出n個點m條單向邊邊以及經過每條邊的費用,讓你求出走過一個哈密頓環(除起點外,每個點只能走一次)的最小費用。

解析:

任意類似的【有向環最小權值覆蓋】問題,都可以用最小費用流來寫。

由於題目中要求每個點最多走一次,為了防止走多次的發生,我們要把每個點 i 拆成左點i 和 右點i + n兩個點

具體建圖如下:

源點outset編號0, 所有左點編號 1~n ,右點編號 n+1 ~ 2*n, 匯點inset 編號 2*n+1.(1)源點outset到第i個點有邊 ( outset, i, 1, 0),即源點和左點建邊。(2)如果從 i 點到 j 點有權值為 c 的邊,那麼有邊  (i,  j+n,  1,  c),即左點和右點建邊, 確保了每個點只走一次。

(3)每個節點到匯點有邊 (i+n,  inset,  1,  0), 即右點和匯點建邊。 最終如果最大流 == n 的話(即滿流),那麼最小費用就是我們所求;若最大流量小於n,則不存在滿足條件的環

為什麼這樣的構圖方法就可以求得我們所要的解, 具體解析請點這裡:解析

而且本題時間要求比較嚴格,而且點少邊多;各種超時,需要加個去重邊處理才行。即先用鄰接矩陣儲存最小邊權,然後再建圖

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<queue>
#include<cstdio>
#include<string>
#include<math.h>
#include<algorithm>
#include<map>
#include<set>
#include<stack>
#define mod 998244353
#define INF 0x3f3f3f3f
#define eps 1e-6
using namespace std;
typedef long long ll;
#define MAXN 1003
#define MAXM 40004
//最小費用最大流
struct Edge{
    int to,next;
    int flow,cost,cap;
}edge[MAXM];
int tol,head[MAXN];
void init()
{
    tol=0;
    memset(head,-1,sizeof head);
}
void addEdge(int u,int v,int cap,int cost){
    edge[tol].to=v;
    edge[tol].cap=cap;
    edge[tol].cost=cost;
    edge[tol].flow=0;
    edge[tol].next=head[u];
    head[u]=tol++;

    edge[tol].to=u;
    edge[tol].cap=0;
    edge[tol].cost=-cost;
    edge[tol].flow=0;
    edge[tol].next=head[v];
    head[v]=tol++;
}

bool inq[MAXN];//標記是否點是否在佇列
int dis[MAXN];//最短距離
int pre[MAXN];//記錄路徑
int q[MAXN*10];//佇列
//單位費用可能是負值,所以用SPFA
bool spfa(int st,int en)
{
    memset(inq,0,sizeof inq);
    memset(dis,INF,sizeof dis);
    memset(pre,-1,sizeof pre);

    int rear=0,front=0;
    dis[st]=0;
    inq[st]=true;
    q[front++]=st;
    while(rear<front){
        int u=q[rear++];
        inq[u]=false;

        for(int e=head[u];e!=-1;e=edge[e].next){
            int v=edge[e].to;
            if(edge[e].cap>edge[e].flow&&dis[v]>dis[u]+edge[e].cost){
                dis[v]=dis[u]+edge[e].cost;
                pre[v]=e;//表示邊e-->v,e就是v的前驅
                if(!inq[v])
                    inq[v]=true,q[front++]=v;
            }
        }
    }
    return pre[en]!=-1;
}
int MCMF(int st,int en,int &cost,int &flow)
{
    //如果能找到從源點到匯點的最短路,說明還沒有達到最小費用最大流
    while(spfa(st,en)){
        int Min=INF;//最小殘餘流量
        //沿著當前路徑返回
        for(int i=pre[en];i!=-1;i=pre[edge[i^1].to]){
            int rem=edge[i].cap-edge[i].flow;
            Min=Min>rem?rem:Min;
        }
        for(int i=pre[en];i!=-1;i=pre[edge[i^1].to]){
            edge[i].flow+=Min;//正向邊新增殘餘流量
            edge[i^1].flow-=Min;//反向邊減少殘餘流量
            cost+=Min*edge[i].cost;
        }
        flow+=Min;
    }
}
//以上為最小費用最大流模板
int mp[210][210];
int main()
{
    int n,m;
    int T;
    scanf("%d",&T);
    while(T--){
      scanf("%d%d",&n,&m);
        memset(mp,INF,sizeof mp);
        init();
        int st=0,en=2*n+1;
        for(int i=1;i<=n;i++){
            addEdge(st,i,1,0);
            addEdge(n+i,en,1,0);
        }
        //去重邊操作
        for(int i=1;i<=m;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            mp[u][v]=min(mp[u][v],w);
            //addEdge(u,n+v,1,w);
        }
        //建圖
        for(int i=1;i<=n;i++){
          for(int j=1;j<=n;j++){
            if(mp[i][j]!=INF)
            addEdge(i,j+n,1,mp[i][j]);
          }
        }
        int cost=0,flow=0;
        MCMF(st,en,cost,flow);
        //printf("%d  %d\n",flow,cost);

        if(flow==n)
            printf("%d\n",cost);
        else
            printf("-1\n");
    }
}