• [例题/总结]0/1分数规划



    一、总述

    0/1分数规划是专门解决0/1分数规划模型的一种算法(废话)。所以说0/1分数规划模型是什么呢?给定整数{(a_1,a_2,a_3,...,a_n)},{(b_1,b_2,b_3,...,b_n)}从中选出若干对数,使得它们各自和的比值最大。公式如下:

    [frac{sum_{p=1}^{n}a_p imes x_p}{sum_{p=1}^{n}b_p imes x_p}(x_p=1,0) ]

    二、实现原理

    那么我们用什么方法可以求出这样一个看上去十分复杂的柿子呢?正确的答案是二分法,但是目前来看求解该式的最大值与二分法无关。
    我们可以任意猜测一个比值(Q),此时分为两种情况讨论:

    1. (exists){(x_1,x_2,...,x_n)},使得(frac{sum_{p=1}^{n}a_p imes x_p}{sum_{p=1}^{n}b_p imes x_p}(x_p=1,0)geqslant Q)
      通过计算发现存在解使得答案大于(Q),这说明此时我们的(Q)猜小了,(Q)还可以继续变大。

    2. (forall){(x_1,x_2,...,x_n)},使得(frac{sum_{p=1}^{n}a_p imes x_p}{sum_{p=1}^{n}b_p imes x_p}(x_p=1,0)lt Q)
      所有的答案都比(Q)小,可以得出我们的(Q)枚举的大了,需要减少。

    这个求解(Q)的过程是不是很熟悉?机智的你一定发现(Q)具有二分性,这和二分答案方法是一样的,至此我们可以用二分答案解决这个问题。

    那么如何计算是否存在这样的比值大于我们枚举的(Q)呢?显然,直接计算这个比值是极其不明智的选择。这时需要我们对公式进行一个小小的变形。
    观察这个式子:

    [frac{sum_{p=1}^{n}a_p imes x_p}{sum_{p=1}^{n}b_p imes x_p}(x_p=1,0)geqslant Q ]

    我们把分母乘到等式右边:

    [sum_{p=1}^{n}a_p imes x_pgeqslant sum_{p=1}^{n}Q imes b_p imes x_p ]

    在把右边的柿子移到左边来:

    [sum_{p=1}^{n}a_p imes x_p - sum_{p=1}^{n}Q imes b_p imes x_pgeqslant 0 ]

    提取公因式得到最终公式:

    [sum_{p=1}^{n}(a_p-Q imes b_p) imes x_pgeqslant 0 ]

    现在我们知道怎么做了,由于只要存在一组解就可以,那么我们只要求出这个式子的最大值并判断这个值是否大于0。大于0说明(Q)不够大,小于0说明(Q)太大了,用二分法不断逼近答案直到达到合适的精度。

    三、例题

    以下例题不是十分困难,稍复杂的地方就是二分法check( )函数的写法。

    例1:POJ2976 Dropping tests(原POJ2519)

    题目疯狂明示让你用01分数规划。二分枚举成绩,求出每一项的值并排序(要求最大值),如果答案小于0那么更改左区间,反之更改右区间。
    Code:

    #include<bits/stdc++.h>
    #define N 2000
    using namespace std;
    int n,k;
    double a[N],b[N],f[N];
    double check(double mid)
    {
    	memset(f,0,sizeof(f));
    	for(int i=1;i<=n;i++)
    		f[i]=a[i]-mid*b[i];//转化后的公式
    	sort(f+1,f+n+1,greater<double>());
    	double sum=0;
    	for(int i=1;i<=n-k;i++)
    		sum+=f[i];//求一下最大值
    	return (sum>0)? 1:0;
    }
    int main()
    {
    	while(scanf("%d%d",&n,&k)&&n+k)
    	{
    		memset(a,0,sizeof(a));
    		memset(b,0,sizeof(b));
    		for(int i=1;i<=n;i++) scanf("%lf",&a[i]);
    		for(int i=1;i<=n;i++) scanf("%lf",&b[i]);
    		double l=0,r=1e10;
    		while(r-l>1e-8){
    			double mid=(l+r)/2;
    			if(check(mid)) l=mid;
    			else r=mid;
    		}
    		cout<<fixed<<setprecision(0)<<l*100<<endl;
    	}
    	return 0;
    }
    

    例2:P1730 最小密度路径

    对所有点对进行一次01分数规划,接下来跑最短路判断枚举的密度是否可行,最终求得最小密度路径。

    #include<bits/stdc++.h>
    #define N 10010
    #define INF 0x3f3f3f3f
    #define eps 1e-6
    #define ll long long
    using namespace std;
    double ans[N][N],dist[N],maxn,cost[N];
    int q,n,m,tot,vis[N];
    int first[N],go[N],next[N];
    inline void add_edge(int u,int v,double w){
    	next[++tot]=first[u];
    	first[u]=tot;
    	go[tot]=v;
    	cost[tot]=w;
    }
    inline int check(int s,int ed,double mid){
    	queue<int> q;
    	for(int i=1;i<=n;i++){
    		dist[i]=INF;vis[i]=0;
    	}
    	q.push(s);vis[s]=1;dist[s]=0;
    	while(!q.empty()){
    		int u=q.front();
    		q.pop();vis[u]=0;
    		for(int e=first[u];e;e=next[e]){
    			int v=go[e];double w=cost[e];
    			if(dist[v]>dist[u]+w-mid){
    				dist[v]=dist[u]+w-mid;
    				if(!vis[v]){
    					q.push(v);
    					vis[v]=1;
    				}
    			}
    		}
    	}
    	return (dist[ed]>0)? 1:0;//求的最小比值大于枚举值,更新l,否则更新r
    }
    inline void erfen(){
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++){
            	if(i==j) continue;
    			check(i,j,0);
                if(dist[j]==INF){ans[i][j]=-1;continue;}
                long double l=0,r=maxn;
                while(r-l>eps){
                    long double mid=(l+r)/(2.0);
                    if(!check(i,j,mid)) r=mid;
                    else l=mid;
                }
                ans[i][j]=l;
            }
    }
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1,u,v,w;i<=m;i++){
    		scanf("%d%d%d",&u,&v,&w);
    		add_edge(u,v,(double)w);
    		maxn+=w;
    	}
    	erfen(); 
    	scanf("%d",&q);
    	for(int i=1,u,v;i<=q;i++){
    		scanf("%d%d",&u,&v);
    		ans[u][v]<0?printf("OMG!
    "):printf("%.3lf
    ",ans[u][v]);
    	}
    	return 0;
    }
    

    例3:CF489E Hiking

    基础题。
    Code:

    #include<bits/stdc++.h>
    #define N 100010
    using namespace std;
    const double INF=1e15;
    int n,last[N];
    double len,pos[N],w[N],f[N];
    vector<int> v;
    double check(double mid)
    {
    	for(int i=1;i<=n;i++){
    		f[i]=INF;
    		for(int j=0;j<i;j++){
    			if(f[i]>f[j]+sqrt(fabs(pos[i]-pos[j]-len))-mid*w[i]){
    				f[i]=f[j]+sqrt(fabs(pos[i]-pos[j]-len))-mid*w[i];
    				last[i]=j;
    			}
    		}
    	}
    	return (f[n]<=0)?1:0;
    }
    int main()
    {
    	scanf("%d%lf",&n,&len);
    	for(int i=1;i<=n;i++)
    		scanf("%lf%lf",&pos[i],&w[i]);
    	double l=0,r=1e10;
    	while(r-l>=1e-9){
    		double mid=(l+r)/2;
    		if(check(mid)) r=mid;
    		else l=mid;
    	}
    	check(l);
    	int now=n;
    	while(now>0){
    		v.push_back(now);
    		now=last[now];
    	}
    	for(int i=v.size()-1;i>=0;i--)
    		printf("%d ",v[i]);
    	return 0;
    }
    

    例4:P2868 [USACO07DEC]观光奶牛Sightseeing Cows

    此题同P1768 天路类似。
    奶牛们最终要回到起点,我们同样枚举一个比值(Q),可以知道如果变形后不等式大于0,也就是说存在环,那么更新左区间端点(l)。判断一个环可以转换成负环处理,即用SPFA判负环。

    #include<bits/stdc++.h> 
    #define N 200010
    using namespace std;
    int first[N],next[N],go[N],cost[N],vis[N];
    int m,n,tot;
    double dist[N],len[N],f[N];
    inline void add_edge(int u,int v,int w){
        next[++tot]=first[u];
        first[u]=tot;
        go[tot]=v;
        cost[tot]=w;
    }
    double SPFA(int u)//判负环
    {
        vis[u]=1;
        for(int i=first[u];i;i=next[i])
        {
            int v=go[i];
    		double w=len[i];
            if(dist[v]>dist[u]+w)
            {
                dist[v]=dist[u]+w;
                if(vis[v]||SPFA(v)){
                    vis[v]=0;
                    return 1;
                }
            }
        }
    	vis[u]=0;
    	return 0;
    }
    double check(double mid)
    {
    	for(int i=1;i<=tot;i++)
    		len[i]=(double)cost[i]*mid-f[go[i]];
        for(int i=1;i<=n;i++){
    		if(SPFA(i)) return 1;
    	}
        return 0;
    }
    int main()
    {
    	scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
    		scanf("%lf",&f[i]);
        for(int i=1;i<=m;i++){
        	int u,v,w;
    		scanf("%d%d%d",&u,&v,&w);
            add_edge(u,v,w);
        }
        double l=0,r=1e6;
        while(r-l>1e-6){
            double mid=(l+r)/2;
            if(check(mid))
    			l=mid;
            else r=mid;
        }
        cout<<fixed<<setprecision(2)<<l<<endl;
        return 0;
    }
    

    pic.png

  • 相关阅读:
    SpringCloud
    Springboot笔记
    SpringMVC学习随笔
    Spring中事务的理解
    Spring中Aop的理解
    FLutter 各种项目的目录创建
    flutter 打包Android 创建签名证书
    flutter web网站 第一次访问首屏页 空白卡顿 再显示
    flutter--报错--Flutter 调试时卡在 Installing buildappoutputsapkapp.apk
    flutter 顶部状态栏 底部栏 显示和隐藏
  • 原文地址:https://www.cnblogs.com/cyanigence-oi/p/11766432.html
Copyright © 2020-2023  润新知