1. 程式人生 > >【POJ3155】Hard Life-01分數規劃+最小割

【POJ3155】Hard Life-01分數規劃+最小割

測試地址:Hard Life
題目大意:有一個無向圖,要從裡面選出一個子圖,使得邊數和點數的比最大,輸出一個合法方案。
做法:本題需要用到01分數規劃+最小割。
首先要求比值最大,我們立刻想到01分數規劃的套路,二分比值,這樣就變成判定性問題:存不存在一個子圖使得|E|k|V|>0
怎麼樣選出一個合法的子圖?注意到,如果我們選了一條邊,那麼這條邊的兩個端點必須全部選上,而且選一條邊的收益是1,選一個點的成本是k。要求收益最大的話,這顯然就是一個最大權閉合子圖的模型了。求出最大流後,沿著殘餘網路能到達的點就是我們選擇的子圖的邊和點了。
這題有一個非常噁心的地方就是精度。因為你在二分的時候,左右端點無限逼近最優點,但只要稍微偏移一點,就根本不存在方案,所以我們令出二分後的左端點為

l,我們應該令k=leps再做一次最大流,這樣就能保證一定能取到一個方案。注意上述問題後,再特判答案為0的情況(即m=0)即可。
以下是本人程式碼:

#include <bits/stdc++.h>
using namespace std;
const double inf=1000000000.0;
const double eps=1e-8;
int n,m,S,T,first[2010]={0},tot=1,last;
int lvl[2010],cur[2010],h,t,q[2010];
int cnt,ans[2010];
bool vis[2010]={0};
struct edge
{
    int
v,next; double f; }e[50010]; void insert(int a,int b,double f) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot; e[++tot].v=a,e[tot].next=first[b],e[tot].f=0.0,first[b]=tot; } void init() { scanf("%d%d",&n,&m); S=n+m+1,T=n+m+2; for(int i=1;i<=m;i++) { int
a,b; insert(S,n+i,1.0); scanf("%d%d",&a,&b); insert(n+i,a,inf); insert(n+i,b,inf); } last=tot; for(int i=1;i<=n;i++) insert(i,T,0.0); } bool makelevel() { for(int i=1;i<=T;i++) lvl[i]=-1,cur[i]=first[i]; lvl[S]=0; h=t=1; q[1]=S; while(h<=t) { int v=q[h++]; for(int i=first[v];i;i=e[i].next) if (e[i].f>eps&&lvl[e[i].v]==-1) { lvl[e[i].v]=lvl[v]+1; q[++t]=e[i].v; } } return lvl[T]!=-1; } double maxflow(int v,double maxf) { double ret=0.0,f; if (v==T) return maxf; for(int i=cur[v];i;i=e[i].next) { if (e[i].f>eps&&lvl[e[i].v]==lvl[v]+1) { f=maxflow(e[i].v,min(maxf-ret,e[i].f)); ret+=f; e[i].f-=f; e[i^1].f+=f; if (maxf-ret<eps) break; } cur[v]=i; } if (ret<eps) lvl[v]=-1; return ret; } bool dinic() { double maxf=0.0; while(makelevel()) maxf+=maxflow(S,inf); return (double)m-maxf>eps; } void findsol() { h=t=1; q[1]=S; while(h<=t) { int v=q[h++]; for(int i=first[v];i;i=e[i].next) if (e[i].f>eps&&!vis[e[i].v]) { vis[e[i].v]=1; q[++t]=e[i].v; } } } void modify(double x) { tot=1; for(int i=1;i<=m;i++) { e[++tot].f=1.0,e[++tot].f=0.0; e[++tot].f=inf,e[++tot].f=0.0; e[++tot].f=inf,e[++tot].f=0.0; } for(int i=1;i<=n;i++) e[++tot].f=x,e[++tot].f=0.0; } void work() { if (!m) {printf("1\n1");return;} double l=0.0,r=(double)m; while(r-l>=eps) { double mid=(l+r)/2.0; modify(mid); if (dinic()) l=mid; else r=mid; } modify(l-eps); dinic(); findsol(); cnt=0; for(int i=last+1,j=1;i<=tot;i+=2,j++) if (vis[j]) ans[++cnt]=j; printf("%d\n",cnt); for(int i=1;i<=cnt;i++) printf("%d\n",ans[i]); } int main() { init(); work(); return 0; }