[BZOJ2285][Sdoi2011]保密(01分数规划+最小割)

本文介绍了如何利用01分数规划和最小割方法解决[BZOJ2285][Sdoi2011]保密问题。首先通过二分答案和SPFA判断是否存在更优解,接着利用题目给定的点构造图并建立边,最后通过最小割算法找到最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

传送门

题解

首先求出来n到1~n1的最短路,这个需要用到01分数规划
首先二分答案mid,那么假设 aibimid ,也就是当 aibimid0 的时候说明还存在更优的答案
那么将每一条边的边权变成a-b*mid,然后spfa判断是否有负权的路径就行了
注意这里spfa只要有负权路就直接退出,会快很多
这样做n1次就求出了1~n1的答案

然后题目中都已经给你建好了两排点
对于一个空腔连着的两个点x,y,连边x->y,inf
然后对于奇数点,s->i,dis(i);对于偶数点,i->t,dis(t),其中dis即为上面求出来的答案
跑最小割就行了

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;

const double eps=1e-3;
const double inf=1e9;
int dcmp(double x)
{
    if (x<=eps&&x>=-eps) return 0;
    return (x>0)?1:-1;
}
struct data{int x,y;double a,b;}e[100005];
int n,m,n1,m1,s,t;
double maxflow;
double dis[705];

namespace plan
{
    int tot,point[705],nxt[100005],v[100005];double c[100005];
    double dis[705];bool vis[705];
    queue <int> q;

    void add(int x,int y)
    {
        ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
    }
    void spfa(int id)
    {
        memset(dis,127,sizeof(dis));dis[n]=0;
        memset(vis,0,sizeof(vis));vis[n]=1;
        q.push(n);
        while (!q.empty())
        {
            int now=q.front();q.pop();
            vis[now]=0;
            for (int i=point[now];i;i=nxt[i])
                if (dcmp(dis[v[i]]-dis[now]-c[i])>0)
                {
                    dis[v[i]]=dis[now]+c[i];
                    if (v[i]==id&&dcmp(dis[v[i]])<=0) return;
                    if (!vis[v[i]]) vis[v[i]]=1,q.push(v[i]);
                }
        }
    }
    bool check(int id,double mid)
    {
        for (int i=1;i<=m;++i) c[i]=e[i].a-e[i].b*mid;
        spfa(id);
        return dcmp(dis[id])<=0;
    }
    double find(int id)
    {
        double l=0,r=10,mid,ans=inf;
        while (r-l>eps)
        {
            mid=(l+r)/2.0;
            if (check(id,mid)) ans=r=mid;
            else l=mid;
        }
        return ans;
    }
}
namespace Flow
{
    int tot,point[705],nxt[100005],v[100005];double remain[100005];
    int deep[705],cur[705];
    queue <int> q;

    void init()
    {
        tot=-1;
        memset(point,-1,sizeof(point));
    }
    void addedge(int x,int y,double cap)
    {
        ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=cap;
        ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0;
    }
    bool bfs(int s,int t)
    {
        for (int i=1;i<=t;++i) deep[i]=t;
        deep[s]=0;
        for (int i=1;i<=t;++i) cur[i]=point[i];
        q.push(s);
        while (!q.empty())
        {
            int now=q.front();q.pop();
            for (int i=point[now];i!=-1;i=nxt[i])
                if (deep[v[i]]==t&&dcmp(remain[i]))
                {
                    deep[v[i]]=deep[now]+1;
                    q.push(v[i]);
                }
        }
        return deep[t]<t;
    }
    double dfs(int now,int t,double limit)
    {
        if (now==t||!dcmp(limit)) return limit;
        double flow=0,f;
        for (int i=point[now];i!=-1;i=nxt[i])
        {
            cur[now]=i;
            if (deep[v[i]]==deep[now]+1&&dcmp(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;
    }
    void dinic(int s,int t)
    {
        while (bfs(s,t))
            maxflow+=(dfs(s,t,inf));
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;++i)
    {
        scanf("%d%d%lf%lf",&e[i].x,&e[i].y,&e[i].a,&e[i].b);
        plan::add(e[i].x,e[i].y);
    }
    scanf("%d%d",&m1,&n1);
    for (int i=1;i<=n1;++i)
        dis[i]=plan::find(i);
    Flow::init();
    s=n+1,t=s+1;
    for (int i=1;i<=n1;++i)
    {
        if (i&1) Flow::addedge(s,i,dis[i]);
        else Flow::addedge(i,t,dis[i]);
    }
    for (int i=1;i<=m1;++i)
    {
        int x,y;scanf("%d%d",&x,&y);
        Flow::addedge(x,y,inf);
    }
    Flow::dinic(s,t);
    if (maxflow>1e8) puts("-1");
    else printf("%.1lf\n",maxflow);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值