zoukankan      html  css  js  c++  java
  • 浅谈01分数规划

    首先引出0/1分数规划的概念

    给出一列数$a_i$与$b_i$,构造出一列数$x_i$,使得$frac{sum^{n}_{i=1}{a_i*{x_i}}}{sum^{n}_{i=1}{b_i*{x_i}}}$最大,求出这个最大值(满足$x_iinlbrace{0,1} brace$)

    想要求出这个最大值一般使用二分答案

    即当当前枚举的答案是$mid$时

    判断是否有$frac{sum^{n}_{i=1}{a_i*{x_i}}}{sum^{n}_{i=1}{b_i*{x_i}}}geq mid$

    如果有则$l=mid$,否则$r=mid$

    那么如何快速求出不等式的左边呢?我们将原不等式变形

    $$ sum^n_{i=1}{a_i*x_i}geq {mid*sum^n_{i=1}{b_i*x_i}} $$

    $$ sum^n_{i=1}{a_i*x_i}-{mid*sum^n_{i=1}{b_i*x_i}}geq 0 $$

    不等式左边将$x_i$提出去

    $$x_i*(sum_{i=1}^n{a_i-mid*b_i})geq 0$$

    因此我们可以在$O(n)$的时间内将$a_i-mid*b_i$处理出来,然后做一个简单的贪心

    如果这个值$geq 0$,那么令这个$x_i=1$,否则令$x_i=0$

    关于总的时间复杂度,二分答案$O(logn)$,检验答案合法性$O(n)$,总时间复杂度为$O(nlogn)$

    一道例题

    题目链接poj2976

    看到式子就知道这大概是个模板题了

    唯一的不同是这里只要求选出$n-k+1$个数

    那么只要将所有的$sum_{i=1}^n{a_i-mid*b_i}$排序,直接选取前$n-k+1$个数相加即可

    由于保证$a_i<b_i$,所以二分边界只要是$[0,1]$即可

     1 #include<iostream>
     2 #include<string>
     3 #include<string.h>
     4 #include<stdio.h>
     5 #include<algorithm>
     6 #include<vector>
     7 #include<queue>
     8 #include<map>
     9 using namespace std;
    10 int a[1010],b[1010],n,k;
    11 double sum[1010];
    12 
    13 bool check(double num)
    14 {
    15     int i;
    16     for (i=1;i<=n;i++) sum[i]=(double)a[i]-num*b[i];
    17     sort(sum+1,sum+1+n);
    18     double s=0.0;
    19     for (i=k+1;i<=n;i++) s+=sum[i];
    20     if (s>=0) return 1;else return 0;
    21 }
    22 
    23 int main()
    24 {
    25     scanf("%d%d",&n,&k);
    26     while ((n!=0) || (k!=0))
    27     {
    28         int i;
    29         for (i=1;i<=n;i++) scanf("%d",&a[i]);
    30         for (i=1;i<=n;i++) scanf("%d",&b[i]);
    31         double l=0.0,r=1.0;
    32         while (r-l>1e-4)
    33         {
    34             double mid=(l+r)/2;
    35             if (check(mid)) l=mid; else r=mid;
    36         }
    37         printf("%.0lf
    ",l*100);
    38         scanf("%d%d",&n,&k);
    39     }
    40     return 0;
    41 }

    再来看另一道例题[USACO07DEC]Sightseeing Cows

    我们用$fun[i]$表示在这个点可以获得的快乐值,$time[i]$表示在每条道路上花的时间

    由于要求回答出发点,所以就是要找一个环

    假设环上有$p$个点

    那么我们要求的是$frac{sum^p_{i=1}{fun[i]}}{sum^p_{i=1}{time[i]}}=ans$中的$ans$最大

    同样考虑二分答案

    考虑$frac{sum^p_{i=1}{fun[i]}}{sum^p_{i=1}{time[i]}}> ans$

    原式可改写为$sum^p_{i=1}(fun[i]-ans*time[i])>0$

    在图上跑这个东西并不好跑

    我们改变一下不等式的方向,即$sum^p_{i=1}(ans*time[i]-fun[i])<0$

    于是我们可以把每条边的权值看做$ans*time[i]-fun[i]$

    然后判负环就可以啦

    方法:使用$spfa$,记录下每个点进队的次数,如果多于$n$那么就出现了负环

    还要注意的是:图不一定是连通图,所以起始时可以让每个点都进入队列

    由于判的是负环所以直接$dis[i]=0$就可以啦

    话说在USACO里写spfa不会死吗(逃

     1 #include<iostream>
     2 #include<string>
     3 #include<string.h>
     4 #include<stdio.h>
     5 #include<algorithm>
     6 #include<vector>
     7 #include<queue>
     8 #include<map>
     9 using namespace std;
    10 struct node{
    11     int to,nxt,cost;
    12 }sq[10010];
    13 int n,m,fun[1010],head[1010],all=0,num[1010];
    14 double dis[1010];
    15 bool vis[1010];
    16 
    17 void add(int u,int v,int w)
    18 {
    19     all++;sq[all].to=v;sq[all].nxt=head[u];sq[all].cost=w;head[u]=all;
    20 }
    21 
    22 bool check(double ans)
    23 {
    24     int i;
    25     queue<int> q;
    26     for (i=1;i<=n;i++)
    27     {
    28         q.push(i);
    29         vis[i]=1;num[i]=1;
    30         dis[i]=0.0;
    31     }
    32     while (!q.empty())
    33     {
    34         int i,u=q.front();q.pop();
    35         vis[u]=0;
    36         for (i=head[u];i;i=sq[i].nxt)
    37         {
    38             int v=sq[i].to;
    39             if (dis[v]>(double)sq[i].cost*ans-fun[u]+dis[u])
    40             {
    41                 dis[v]=(double)sq[i].cost*ans-fun[u]+dis[u];
    42                 if (!vis[v])
    43                 {
    44                     num[v]++;
    45                     if (num[v]>=n) return 1;
    46                     vis[v]=1;q.push(v);
    47                 }
    48             }
    49         }
    50     }
    51     return 0;
    52 }
    53 
    54 int main()
    55 {
    56     scanf("%d%d",&n,&m);
    57     int i;
    58     for (i=1;i<=n;i++) scanf("%d",&fun[i]);
    59     for (i=1;i<=m;i++)
    60     {
    61         int u,v,w;
    62         scanf("%d%d%d",&u,&v,&w);
    63         add(u,v,w);
    64     }
    65     double l=0.0,r=1001000.0;
    66     while (r-l>1e-4)
    67     {
    68         double mid=(l+r)/2;
    69         if (check(mid)) l=mid; else r=mid;
    70     }
    71     printf("%0.2lf",l);
    72     return 0;
    73 }







  • 相关阅读:
    线程池1-线程池原理
    CompletableFuture 详解
    服务崩溃的本质
    关于C#读取MySql数据时,返回DataTable中某字段数据是System.Array[]形式
    关于VS2010中的TraceDebugging文件夹浅说
    C#更改win7系统时间的代码,以及为什么更改不成功
    在DataColumn.Expression把DateTime转换成String的问题
    C#用委托实现异步,异步与多线程的异同
    DataColumn.Expression提示“...循环引用”的错误
    JDBC插入百万数据,不到5秒!
  • 原文地址:https://www.cnblogs.com/encodetalker/p/9763065.html
Copyright © 2011-2022 走看看