HDU 1853 & HDU 3488【有向環最小權值覆蓋問題 】最小費用最大流
阿新 • • 發佈:2018-12-14
題意:
給出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"); } }