[ZOJ2676]最小割+01分数规划

题目描述

在这里插入图片描述

分析
  • 看完题目居然没什么思路,菜的真实233
  • 哎这显然是一个01分数规划吧首先。什么?你不会01分数规划?好吧我也不太会了,我们来回顾一下。
  • 对于题目的这个式子,我们转化为更一般的: ∑ i = 1 n w i ∗ x i ∑ i = 1 n x i \frac{\sum_{i=1}^nw_i*x_i}{\sum_{i=1}^nx_i} i=1nxii=1nwixi,其中 x i x_i xi为0或1,我们让这个式子最小。我们假设一个L,如果存在一组解,使 ∑ i = 1 n w i ∗ x i ∑ i = 1 n x i &lt; = L \frac{\sum_{i=1}^nw_i*x_i}{\sum_{i=1}^nx_i}&lt;=L i=1nxii=1nwixi<=L,那么L一定大于答案。我们发现答案具有单调性,因此可以二分。
  • 下面是式子的转化:
    ∑ i = 1 n w i ∗ x i ∑ i = 1 n x i &lt; = L \frac{\sum_{i=1}^nw_i*x_i}{\sum_{i=1}^nx_i}&lt;=L i=1nxii=1nwixi<=L
    = &gt; ∑ i = 1 n ( w i − L ) ∗ x i &lt; = 0 =&gt; \sum_{i=1}^n(w_i-L)*x_i&lt;=0 =>i=1n(wiL)xi<=0
  • 这样的话,有思路了吧。每次二分一个L,把边权减去L,找出s到t的最小割,如果最小割+边权为负的权值<=0,那么说明L不是答案,让r=mid。否则,让l=mid。
  • 思路就讲到这里,具体细节,自己实现的时候注意一下。
Coding
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#define Maxn 23333//数组大小随缘开的233
#define Maxe 23333
using namespace std;
inline int Getint(){int x=0,f=1;char ch=getchar();while('0'>ch||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while('0'<=ch&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
int n,m,S,T,N,cnt=0,x[Maxn],y[Maxn],v[Maxn],h[Maxn],GAP[Maxn],dis[Maxn];
bool vis[Maxn],used[Maxn];
struct node{int to,next,v,pair;}e[Maxe];
void AddEdge(int X,int Y,int v,int pa){e[cnt]=(node){Y,h[X],v,pa};h[X]=cnt;}
void AddEdge(int X,int Y,int v){AddEdge(X,Y,v,++cnt+1);AddEdge(Y,X,0,++cnt-1);}
int SAP(int X,int Maxflow){
    if(X==T)return Maxflow;
    int tmp=Maxflow;
    for(int p=h[X];p;p=e[p].next){
        int y=e[p].to;
        int flow=min(tmp,e[p].v);
        if(flow&&dis[X]==dis[y]+1){
            int ret=SAP(y,flow);
            tmp-=ret;
            e[p].v-=ret;
            e[e[p].pair].v+=ret;
            if(!tmp||dis[S]==n)return Maxflow-tmp;
        }
    }
    if(--GAP[dis[X]]==0)dis[S]=n;
    else GAP[++dis[X]]++;
    return Maxflow-tmp;
}
double SAP(){
    memset(GAP,0,sizeof(GAP));
    memset(dis,0,sizeof(dis));
    GAP[0]=n;
    S=1,T=n;
    int Ans=0;
    while(dis[S]<n)Ans+=SAP(S,1<<30);
    return Ans;
}
bool Check(double k){
    memset(e,0,sizeof(e));
    memset(h,0,sizeof(h));
    memset(used,0,sizeof(vis));
    cnt=0;
    double ret=0.0;
    for(int i=1;i<=m;i++){
        if(v[i]<=k){used[i]=1;ret+=v[i]-k;}
        else{AddEdge(x[i],y[i],v[i]-k);AddEdge(y[i],x[i],v[i]-k);}//这里的v[i]要减去K
    }
    return SAP()+ret>0;
}
void Init(){
    for(int i=1;i<=m;i++)x[i]=Getint(),y[i]=Getint(),v[i]=Getint();
}
void Dfs(int x){
    vis[x]=true;
    for(int p=h[x];p;p=e[p].next){
        int y=e[p].to;
        if(!vis[y]&&e[p].v)Dfs(y);
    }
}
void Solve(){
    double L=0,r=1e9;
    while(L+1e-8<r){//二分C/K的值,精读太低就会WA。。。
        double mid=(L+r)/2;
        if(Check(mid))L=mid;
        else r=mid;
    }
    memset(vis,0,sizeof(vis));
    Dfs(S);
    for(int i=1;i<=m;i++)if(vis[x[i]]!=vis[y[i]])used[i]=true;
    int Ans=0;
    for(int i=1;i<=m;i++)Ans+=used[i];
    cout<<Ans<<"\n";
    for(int i=1;i<=m;i++)if(used[i])cout<<i<<" ";
    cout<<"\n";
}
int main(){
    while(scanf("%d%d",&n,&m)!=EOF){
        Init();
        Solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值