bzoj 2285: [Sdoi2011]保密 (二分+SPFA+dinic)

91 篇文章 0 订阅
41 篇文章 0 订阅

题目描述

传送门

题解

对于1..n1中的每个点都要最小化n到该点经过的所有道路的 tisi
这个应该是01分数规划问题,二分答案,使边权变成 timidsi ,判断图中是否有n到该点的权值为负的路径,有的话说明答案还可以再小。
那么对于每个点求出 val[x]=min{tisi} ,然后建立最小割的模型。
i为奇数:S->i 容量为val[i]
i为偶数:i->T 容量为val[i]
如果两个点之间有空腔:x->y 容量为inf
这样至少会割掉两个端点中的一个,保证每条空腔都被覆盖。

代码

#include<iostream>  
#include<cstdio>  
#include<algorithm>  
#include<cstring>  
#include<cmath>  
#include<queue>
#define N 1000003  
#define eps 1e-5
using namespace std;  
const double inf=1e12;
int point[N],nxt[N],v[N],S,T,can[N],n,m,n1,m1,tot,mark;  
double a[N],b[N],dis[N],val[N]; 
struct data{  
    int point[N],nxt[N],v[N],cur[N],tot,deep[N];  
    double  remain[N];
    void add(int x,int y,double z){  
        tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=z;  
        tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0; 
    //  cout<<x<<" "<<y<<" "<<z<<endl;
    }  
    void init() {  
        tot=-1;  
        memset(point,-1,sizeof(point));  
        S=n1+1; T=S+1;  
    }  
    void build(){
        for (int i=1;i<=n1;i++)
         if (i&1) add(S,i,val[i]);
         else add(i,T,val[i]);
        for (int i=1;i<=m1;i++) {
            int x,y; scanf("%d%d",&x,&y);
            if (val[x]==inf&&val[y]==inf) {
                printf("-1\n");
                mark=1; break;
            }
            add(x,y,inf);
        }
    }
    int dcmp(double x) {
        if (fabs(x)<eps) return 0;
        return 1;
    }
    bool bfs(int s,int t){  
        for (int i=1;i<=t;i++) deep[i]=t+1;  
        for (int i=1;i<=t;i++) cur[i]=point[i];  
        queue<int> p; p.push(s); deep[s]=0;  
        while (!p.empty()) {  
            int now=p.front(); p.pop();  
            for (int i=point[now];i!=-1;i=nxt[i])  
             if (deep[v[i]]==t+1&&dcmp(remain[i]))  
              deep[v[i]]=deep[now]+1,p.push(v[i]);  
        }  
        if (deep[t]==t+1) return false;  
        return true;  
    }  
    double dfs(int now,int t,double limit){  
        if (now==t||!dcmp(limit)) return limit;  
        double flow=0,f;  
        for (int i=cur[now];i!=-1;i=nxt[i]){  
            cur[now]=i;  
            if (deep[v[i]]==deep[now]+1&&(f=dfs(v[i],t,min(remain[i],limit)))) {  
                flow+=f; limit-=f;  
                remain[i]-=f; remain[i^1]+=f;  
                if (!dcmp(limit)) break;  
            }  
        }  
        return flow;  
    }  
    double dinic(int s,int t){  
        double ans=0;  
        while (bfs(s,t)) ans+=dfs(s,t,inf);  
        return ans;  
    }  
}E;  
void add(int x,int y,double a1,double b1)
{
    tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; a[tot]=a1; b[tot]=b1;
}
bool check(double x,int T)
{
    for (int i=1;i<=n;i++) can[i]=0,dis[i]=inf;
    dis[n]=0; can[n]=1;
    queue<int> p; p.push(n);
    while (!p.empty()) {
        int now=p.front(); p.pop();
        for (int i=point[now];i;i=nxt[i]) {
         double k=a[i]-b[i]*x;
         if (dis[v[i]]>dis[now]+k) {
            dis[v[i]]=dis[now]+k;
            if (dis[v[i]]<=0&&v[i]==T) return 1;
            if (!can[v[i]]) {
                can[v[i]]=1;
                p.push(v[i]);
            }
         }
        }
        can[now]=0;
    }
    return dis[T]<=0;
}
int main()  
{  
    freopen("a.in","r",stdin);
    scanf("%d%d",&n,&m);  double sum=0;
    for (int i=1;i<=m;i++) {
        int x,y; double s,t;
        scanf("%d%d%lf%lf",&x,&y,&s,&t);
        add(x,y,s,t); sum+=s;
    }  
    scanf("%d%d",&m1,&n1);
    for (int i=1;i<=n1;i++) {
        double l=0; double r=10.0; double ans=sum;
        while (r-l>=eps) {
            double mid=(l+r)/2;
            if (check(mid,i)) r=mid-eps,ans=min(ans,mid);
            else l=mid+eps;
        }
        if (dis[i]==inf) val[i]=inf;
        else val[i]=ans; //printf("%.3lf ",val[i]);
    }
   // cout<<endl; 
    E.init(); E.build();
    if(mark) return 0;
    printf("%.1lf\n",E.dinic(S,T));
}  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值