01分数规划

01分数规划

POJ 2976 题目链接

题意

n个题目,给出题目的分数(bi)和你的得分(ai),累加平均值的公式为
100 ∗ ∑ i = 1 n a i / ∑ i = 1 n b i 100*\sum_{i=1}^{n}{ai} / \sum_{i=1}^{n}{bi} 100i=1nai/i=1nbi
问你去掉k个题目后,最大的累加平均值为多少

思路

错点:用%.0f不能用%d

二分(速度要稍慢于Dinkelbach 110ms),r的取值范围在[0,1]之间
#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
const double eps = 1e-8;
const int N = 1111;
const int M = 3333;
const int INF = 0x3f3f3f3f;
double a[N],b[N];
double lowcost[N];
double calc(int n,int k,double r)
{
    for (int i = 1; i <= n; i++){
        lowcost[i] = a[i] - r * b[i];
    }
    sort(lowcost + 1,lowcost + 1 + n);
    double ans = 0;
    for (int i = n; i > k; i--){
        ans += lowcost[i];
    }
    return ans;
}
int main()
{
    int n,k;
    while(scanf("%d %d",&n,&k) == 2 && (n || k)){
        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 = 1;
        while(r - l >= eps){
            double mid = (l + r) / 2;
            if (calc(n,k,mid) > 0){
                l = mid + eps;
            }
            else {
                r = mid - eps;
            }
        }
        printf("%.0f\n",(r * 100));
    }
    return 0;
}
Dinkelbach算法(一般r的初始值为0 47ms)
#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
const double eps = 1e-8;
const int N = 1111;
const int M = 3333;
const int INF = 0x3f3f3f3f;
struct node{
    double a,b,cost;
    bool operator < (const node& p) const{
        return cost < p.cost;
    }
}s[N];
double Dinkelbach(int n,int k,double r)
{
    for (int i = 1; i <= n; i++){
        s[i].cost = s[i].a - r * s[i].b;
    }
    sort(s + 1,s + 1 + n);
    double cost = 0,sum = 0;
    for (int i = n; i > k; i--){
        cost += s[i].a;
        sum += s[i].b;
    }
    return cost / sum;
}
int main()
{
    int n,k;
    while(scanf("%d %d",&n,&k) == 2 && (n || k)){
        for (int i = 1; i <= n; i++){
            scanf("%lf",&s[i].a);
        }
        for (int i = 1; i <= n; i++){
            scanf("%lf",&s[i].b);
        }
        double r = 0,l;
        while(true){
            l = Dinkelbach(n,k,r);
            if (fabs(r - l) < eps) break;
            r = l;
        }
        printf("%.0f\n",(r * 100));
    }
    return 0;
}

POJ 2728 题目链接

题意 最优比例树

输入n条村庄,xy代表坐标,h代表高度,两个坐标的高度差就是花费,求出连通所有点(生成树)的总花费与总长度的最小比例(最优比例树)

思路

最 小 化 ∑ i = 1 n − 1 c o s t i / ∑ i = 1 n − 1 l e n i , 总 共 n − 1 条 边 ; 最小化 \sum_{i=1}^{n-1}{cost_i} / \sum_{i=1}^{n-1}{len_i} ,总共n - 1条边; i=1n1costi/i=1n1leni,n1;
令 ∑ i = 1 n − 1 l o w i = ∑ i = 1 n − 1 c o s t i − r ∗ ∑ i = 1 n − 1 l e n i 。 以 l o w i 为 边 , 求 最 小 生 成 树 令 \sum_{i=1}^{n-1}{low_i}=\sum_{i=1}^{n-1}{cost_i} -r* \sum_{i=1}^{n-1}{len_i}。以low_i为边,求最小生成树 i=1n1lowi=i=1n1costiri=1n1lenilowi

Dinkelbach算法(prim求最小生成树 266ms kruskal是1125ms)
#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
const double eps = 1e-8;
const int N = 1111;
const int M = N * N;
const int INF = 0x3f3f3f3f;
struct point{
    double x,y,h;
}p[N];
double len[N][N];
double cost[N][N];
double lowcost[N];
int pre[N];
double Dinkelbach(int n,double r)
{
    for (int i = 1; i < n; i++){
        lowcost[i] = cost[0][i] - r * len[0][i];
        pre[i] = 0;
    }
    double sum_cost = 0,sum_len = 0;
    for (int i = 1; i < n; i++){
        double mi = INF;
        int pos;
        for (int j = 1; j < n; j++){
            if (pre[j] != -1 && lowcost[j] < mi){
                mi = lowcost[j];
                pos = j;
            }
        }
        sum_cost += cost[pos][pre[pos]];
        sum_len += len[pos][pre[pos]];
        pre[pos] = -1;
        for (int j = 1; j < n; j++){
            if (pre[j] != -1 && lowcost[j] > cost[pos][j] - r * len[pos][j]){
                lowcost[j] = cost[pos][j] - r * len[pos][j];
                pre[j] = pos;
            }
        }
    }
    return sum_cost / sum_len;
}
double dis(int i,int j)
{
    return sqrt((p[i].x - p[j].x) * (p[i].x - p[j].x) + (p[i].y - p[j].y) * (p[i].y - p[j].y));
}
int main()
{
    int n;
    while(scanf("%d",&n) == 1 && n){
        for (int i = 0; i < n; i++){
            scanf("%lf %lf %lf",&p[i].x,&p[i].y,&p[i].h);
        }
        for (int i = 0; i < n; i++){
            for (int j = i + 1; j < n; j++){
                len[i][j] = len[j][i] = dis(i,j);
                cost[i][j] = cost[j][i] = fabs(p[i].h - p[j].h);
            }
        }
        double r = 0,l;
        while(true){
            l = Dinkelbach(n,r);
            if (fabs(r - l) < eps) break;
            r = l;
        }
        printf("%.3f\n",r);
    }
    return 0;
}
二分(2454ms)
#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
const double eps = 1e-8;
const int N = 1111;
const int M = N * N;
const int INF = 0x3f3f3f3f;
struct point{
    double x,y,h;
}p[N];
double len[N][N];
double cost[N][N];
double lowcost[N];
bool vis[N];
double Binary(int n,double r)
{
    for (int i = 1; i < n; i++){
        lowcost[i] = cost[0][i] - r * len[0][i];
    }
    double ans = 0;
    memset(vis,false,sizeof(vis));
    for (int i = 1; i < n; i++){
        double mi = INF;
        int pos;
        for (int j = 1; j < n; j++){
            if (!vis[j] && lowcost[j] < mi){
                mi = lowcost[j];
                pos = j;
            }
        }
        vis[pos] = true;
        ans += mi;
        for (int j = 1; j < n; j++){
            if (!vis[j] && lowcost[j] > cost[pos][j] - r * len[pos][j]){
                lowcost[j] = cost[pos][j] - r * len[pos][j];
            }
        }
    }
    return ans;
}
double dis(int i,int j)
{
    return sqrt((p[i].x - p[j].x) * (p[i].x - p[j].x) + (p[i].y - p[j].y) * (p[i].y - p[j].y));
}
int main()
{
    int n;
    while(scanf("%d",&n) == 1 && n){
        for (int i = 0; i < n; i++){
            scanf("%lf %lf %lf",&p[i].x,&p[i].y,&p[i].h);
        }
        for (int i = 0; i < n; i++){
            for (int j = i + 1; j < n; j++){
                len[i][j] = len[j][i] = dis(i,j);
                cost[i][j] = cost[j][i] = fabs(p[i].h - p[j].h);
            }
        }
        double l = 0,r = 2e9;
        while(r - l >= eps){
            double mid = (l + r) / 2;
            if (Dinkelbach(n,mid) > 0){
                l = mid + eps;
            }
            else r = mid - eps;
        }
        printf("%.3f\n",r);
    }
    return 0;
}

POJ 3621 题目链接

题意 最优比例环

给定n个点的权值和m条边的权值(单向),求一个环的回路中,总点权与总边权的最大比例(最优比例环)

思路

在这里插入图片描述
因为Dinkelbach需要分别记录环的总边权和总点权,所以不好写,还是用二分吧

注意点

(1)精度不能太高,设为1e-5就会TLE,精度为1e-3的时候,l和r的值不一样,要输出l,精度为1e-4时随意
(2) r的初始值设为1e9和2e9就会超时,但是设为INF(0x3f3f3f3f)就可以A,明明INF介于两者之间
(3) 题目数据都是可以形成环的,不需要判断也可以

#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
const double eps = 1e-8;
const int N = 1111;
const int M = N * N;
const int INF = 0x3f3f3f3f;
struct point{
    int to,nex;
    double cost;
}e[5 * N];
double value[N];
int head[N],cnt;
double lowcost[N];
bool vis[N];
int num[N];
void add(int u,int v,double cost)
{
    e[cnt].cost = cost;
    e[cnt].to = v;
    e[cnt].nex = head[u];
    head[u] = cnt++;
}
bool Binary(int n,double r)
{
    queue<int> q;
    memset(num,0,sizeof(num));
    memset(vis,false,sizeof(vis));
    for (int i = 1; i <= n; i++){
        lowcost[i] = 1.0 * INF;
    }
    lowcost[1] = 0;
    q.push(1);
    while(!q.empty()){
        int u = q.front();
        q.pop();
        vis[u] = false;
        for (int i = head[u]; i != -1; i = e[i].nex){
            int v = e[i].to;
            if (lowcost[v] > lowcost[u] + r * e[i].cost - 1.0 * value[v]){
                lowcost[v] = lowcost[u] + r * e[i].cost - 1.0 * value[v];
                if (!vis[v]){
                    vis[v] = true;
                    q.push(v);
                    num[v]++;
                    if (num[v] > n) return true;
                }
            }
        }
    }
    return false;
}
int main()
{
    int n,m;
    while(scanf("%d %d",&n,&m) == 2){
        for (int i = 1; i <= n; i++){
            scanf("%lf",&value[i]);
        }
        memset(head,-1,sizeof(head));
        cnt = 0;
        for (int i = 0; i < m; i++){
            int u,v;
            double cost;
            scanf("%d %d %lf",&u,&v,&cost);
            add(u,v,cost);
        }
        double l = 0,r = N * N;
        while(r - l >= 1e-3){
            double mid = (l + r) / 2;
            if(Binary(n,mid)){
                l = mid + eps;
            }
            else r = mid - eps;
        }
        if (l > 0) printf("%.2f\n",l);
        else printf("0\n");
    }
    return 0;
}

POJ 3155 题目链接

参考 算法合集之《最小割模型在信息学竞赛中的应用》

题意 最大密度子图(204ms)

一个公司有n个人,给出了一些有冲突的人的对数(u,v),公司决定裁人,那么总裁现在要裁掉冲突率最高的那些人(冲突率=在这些人中存在的冲突数/人数)。

思路1 最大权闭合子图

因 为 边 依 赖 于 点 , 所 以 就 可 以 转 化 为 最 大 权 闭 合 子 图 模 型 来 求 解 因为边依赖于点,所以就可以转化为最大权闭合子图模型来求解
二 分 值 为 g 时 , 有 h ( g ) = m a x ( ∣ E ′ ∣ − g ∣ V ′ ∣ ) , 选 择 ( u , v ) ∈ ∣ E ′ ∣ , 则 有 u , v ∈ ∣ V ′ ∣ 二分值为g时,有h(g) = max(|E'|-g|V'|),选择(u,v)\in|E'|,则有u,v\in|V'| gh(g)=max(EgV)u,vE,u,vV
将 边 e ( u , v ) 看 成 点 v e , 构 造 最 大 权 闭 合 子 图 模 型 ; 将边e(u,v)看成点v_e,构造最大权闭合子图模型; e(u,v)ve,;
将 原 图 中 的 边 当 成 新 点 连 到 超 级 源 点 , 将 原 图 中 的 点 连 到 超 级 汇 点 将原图中的边当成新点连到超级源点,将原图中的点连到超级汇点
构 成 新 图 ( S t a r t , v e , 1 ) , ( v e , u , I N F ) , ( v e , v , I N F ) , ( u , E N D , g ) , ( v , E N D , g ) 构成新图(Start,v_e,1),(v_e,u,INF),(v_e,v,INF),(u,END,g),(v,END,g ) Start,ve,1),(ve,u,INF),(ve,v,INF),(u,END,g),(v,END,g)
在这里插入图片描述

最大流算法(ISAP模板 )
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
const double eps = 1e-8;
const int N = 2222;
const int M = 2e5 + 5;
const int INF = 0x3f3f3f3f;
struct node{
    int to,nex;
    double len;
}e[M];
struct point{
    int u,v;
}p[N];
int depth[N],gap[N];
int se,ed,V,n,m;
int head[N],cnt;
bool vis[N];
int sum;
void add(int u,int v,double len)
{
    e[cnt].to = v;
    e[cnt].len = len;
    e[cnt].nex = head[u];
    head[u] = cnt++;
}
/// 逆序BFS
void bfs()
{
    memset(gap,0,sizeof(gap));
    memset(depth,-1,sizeof(depth));
    queue<int> q;
    q.push(ed);
    depth[ed] = 0;
    gap[0] = 1;
    while(!q.empty()){
        int u = q.front();
        q.pop();
        for (int i = head[u]; i != -1; i = e[i].nex){
            int v = e[i].to;
            if (depth[v] != -1) continue;
            depth[v] = depth[u] + 1;
            gap[depth[v]]++;
            q.push(v);
        }
    }
}
double dfs(int u,double maxflow)
{
    if (u == ed) return maxflow;
    double ans = 0;
    for (int i = head[u]; i != -1 && ans < maxflow; i = e[i].nex){
        int v = e[i].to;
        if (depth[u] == depth[v] + 1 && e[i].len > 0) {
            double temflow = dfs(v,min(e[i].len,maxflow - ans));
            e[i].len -= temflow;     
            e[i ^ 1].len += temflow;   
            ans += temflow;
            if (ans == maxflow) return ans;
        }
    }
    gap[depth[u]]--;
    if (!gap[depth[u]]) depth[se] = V + 1;
    depth[u]++;
    gap[depth[u]]++;
    return ans;
}
double isap()
{
    double ans = 0;
    bfs();
    while(depth[se] < V){
        ans += dfs(se,INF);
    }
    return 1.0 * m - ans;
}
void build(double g)
{
    memset(head,-1,sizeof(head));
    cnt = 0;
    for (int i = 1; i <= n; i++){
        add(i,ed,g);
        add(ed,i,0);
    }
    for (int i = 0; i < m; i++){
        add(se,n + i + 1,1);
        add(n + i + 1,se,0);
        add(n + i + 1,p[i].u,INF);
        add(p[i].u,n + i + 1,0);
        add(n + i + 1,p[i].v,INF);
        add(p[i].v,n + i + 1,0);
    }
}
void Find(int u)
{
    vis[u] = true;
    if (u >= 1 && u <= n) sum++;
    for (int i = head[u]; i != -1; i = e[i].nex){
        int v = e[i].to;
        if (vis[v] || e[i].len <= 0) continue;
        Find(v);
    }
}
int main()
{
    while(scanf("%d %d",&n,&m) == 2){
        if (m == 0){
            printf("1\n1\n");
            continue;
        }
        for (int i = 0; i < m; i++){
            scanf("%d %d",&p[i].u,&p[i].v);
        }
        se = 0,ed = n + m + 1,V = ed + 1;
        double l = 0,r = m;
        while(r - l >= 1.0 / n / n){
            double mid = (l + r) / 2;
            build(mid);
            if (isap() < eps) r = mid;   /// 不用加eps。不然会wa
            else l = mid;
        }
        build(l);
        isap();
        memset(vis,false,sizeof(vis));
        sum = 0;
        Find(se);
        printf("%d\n",sum);
        for (int i = 1; i <= n; i++){
            if (vis[i]) printf("%d\n",i);
        }
    }
    return 0;
}
思路2 导出子图最小割 (141ms)

对 于 点 集 ∣ V ′ ∣ , ∣ E ′ ∣ = 1 2 ( ∑ v ∈ V ′ d v − C [ V ′ , V ′ ‾ ] ) 对于点集|V'|,|E'| ={{1} \over {2}}(\sum_{v\in V'}{d_v}-C[V', \overline{V'}] ) VE=21(vVdvC[V,V])
d v 表 示 v 的 度 , C [ V ′ , V ′ ‾ ] 表 示 于 V ′ 关 联 但 不 是 E ′ 中 的 边 的 数 量 d_v表示v的度,C[V', \overline{V'}]表示于V'关联但不是E'中的边的数量 dvvC[V,V]VE
将 最 大 化 转 化 成 最 小 化 , 有 − h ( g ) = m i n ( g ∣ V ′ ∣ − ∣ E ′ ∣ ) 将最大化转化成最小化,有-h(g) = min(g|V'|-|E'|) h(g)=min(gVE)
= 1 2 ( ∑ v ∈ V ′ ( 2 g − d v ) + C [ V ′ , V ′ ‾ ] ) = {{1} \over {2}}(\sum_{v\in V'}{(2g-d_v)}+C[V', \overline{V'}] ) =21(vV(2gdv)+C[V,V])
因 为 最 大 流 要 是 非 负 的 流 量 , 所 以 要 加 上 足 够 大 的 数 因为最大流要是非负的流量,所以要加上足够大的数
( U 可 以 取 m , U 的 目 的 是 用 来 使 得 2 ∗ g − d 的 值 始 终 为 正 ) (U可以取m,U的目的是用来使得2*g-d的值始终为正) UmU使2gd
构 图 ( S t a r t , u , U ) , ( S t a r t , v , U ) , ( v , u , 1 ) , ( u , v , 1 ) , ( u , E N D , 2 g − d u ) , ( v , E N D , 2 g − d v ) 构图(Start,u,U),(Start,v,U),(v,u,1),(u,v,1),(u,END,2g-d_u),(v,END,2g-d_v ) Start,u,U),(Start,v,U),(v,u,1),(u,v,1),(u,END,2gdu),(v,END,2gdv)
所 以 h ( g ) = − 最 小 割 − U ∗ n 2 所以h(g) = -{{最小割-U*n} \over {2}} h(g)=2Un
二分找到最优值即为mid ,但是如果要求图中的点则需要用left来从新图求最大流之后然后从源点开始dfs遍历,最后得出结果

#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
const double eps = 1e-8;
const int N = 2222;
const int M = 2e5 + 5;
const int INF = 0x3f3f3f3f;
struct node{
    int to,nex;
    double len;
}e[M];
struct point{
    int u,v;
}p[N];
int depth[N],gap[N];
int se,ed,V,n,m;
int head[N],cnt;
bool vis[N];
int sum,D[N];
void add(int u,int v,double len)
{
    e[cnt].to = v;
    e[cnt].len = len;
    e[cnt].nex = head[u];
    head[u] = cnt++;
}
/// 层次网络
void bfs()
{
    memset(gap,0,sizeof(gap));
    memset(depth,-1,sizeof(depth));
    queue<int> q;
    q.push(ed);
    depth[ed] = 0;
    gap[0] = 1;
    while(!q.empty()){
        int u = q.front();
        q.pop();
        for (int i = head[u]; i != -1; i = e[i].nex){
            int v = e[i].to;
            if (depth[v] != -1) continue;
            depth[v] = depth[u] + 1;
            gap[depth[v]]++;
            q.push(v);
        }
    }
}
/// dfs查找所有增广路并做流量调整
double dfs(int u,double maxflow)
{
    if (u == ed) return maxflow;
    double ans = 0;
    for (int i = head[u]; i != -1 && ans < maxflow; i = e[i].nex){
        int v = e[i].to;
        if (depth[u] == depth[v] + 1 && e[i].len > 0) {
            double temflow = dfs(v,min(e[i].len,maxflow - ans));
            e[i].len -= temflow;     /// 前向弧流量减少
            e[i ^ 1].len += temflow;     /// 反向弧流量增加
            ans += temflow;
            if (ans == maxflow) return ans;
        }
    }
    gap[depth[u]]--;
    if (!gap[depth[u]]) depth[se] = V + 1;
    depth[u]++;
    gap[depth[u]]++;
    return ans;
}
double isap()
{
    double ans = 0;
    bfs();
    while(depth[se] < V){
        ans += dfs(se,INF);
    }
    return ans;
}
void build(double g)
{
    memset(head,-1,sizeof(head));
    cnt = 0;
    for (int i = 1; i <= n; i++){
        add(i,ed,2 * g - D[i]);
        add(ed,i,0);
        add(se,i,m);
        add(i,se,0);
    }
    for (int i = 0; i < m; i++){
        add(p[i].u,p[i].v,1);
        add(p[i].v,p[i].u,0);
        add(p[i].v,p[i].u,1);
        add(p[i].u,p[i].v,0);
    }
}
void Find(int u)
{
    vis[u] = true;
    sum++;
    for (int i = head[u]; i != -1; i = e[i].nex){
        int v = e[i].to;
        if (vis[v] || e[i].len <= 0) continue;
        Find(v);
    }
}
int main()
{
    while(scanf("%d %d",&n,&m) == 2){
        if (m == 0){
            printf("1\n1\n");
            continue;
        }
        memset(D,0,sizeof(D));
        for (int i = 0; i < m; i++){
            scanf("%d %d",&p[i].u,&p[i].v);
            D[p[i].u]++;
            D[p[i].v]++;
        }
        se = 0,ed = n + 1,V = ed + 1;
        double l = 0,r = m;
        while(r - l >= 1.0 / n / n){
            double mid = (l + r) / 2;
            build(mid);
            if ((n * m * 1.0 - isap()) / 2 > eps) l = mid;
            else r = mid;
        }
        build(l);
        isap();
        memset(vis,false,sizeof(vis));
        sum = 0;
        Find(se);
        printf("%d\n",sum - 1);
        for (int i = 1; i <= n; i++){
            if (vis[i]) printf("%d\n",i);
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值