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

    0/1分数规划

    0/1分数规划

    给定一些物品,每个物品有 a[i],b[i] ,要求选 k 个,最大化 (∑ a[i])/(∑b[i]) 。

    二分,判定答案是否能 ≥mid 。

    那么即 (∑a[i])/(∑b[i])≥mid ,也即 ∑a[i]-mid×b[i] ≥0 。

    将物品按照 a[i]-mid×b[i] 排序,若前 k 大的和 ≥0 ,那么答案 ≥mid 。

    最大化的时候,假设最大值为r,那么就有∑pi / ∑si <= r,变形之后就有∑si * r* - ∑pi >= 0.

    最小化的时候,假设最小值为r,那么就有∑pi / ∑si >= r,变形之后就有∑pi - ∑si * r* >= 0.

    形象理解,推荐

    Dinkelbach算法

    https://blog.csdn.net/Cassie_zkq/article/details/81477153

    例:poj2976

    选取n-k(原题是不选k)个物品使得比率最大。

    阿西吧Poj每次都要输出%f才行

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    #define eps 1e-6
    const int N = 10005;
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return f*x;
    }
    
    int n,k;
    struct node{
        double a,b,d;
        bool operator < (const node &x) const {
            return d>x.d;
        }
    }t[N];
    double l,r,mid;
    bool check(double x) {
        double sum=0;
        for(int i=1;i<=n;i++) t[i].d=t[i].a-x*t[i].b;
        sort(t+1,t+1+n);
        for(int i=1;i<=n-k;i++) sum+=t[i].d;
        return sum>=0;
    }
    int main(){
        while(1) {
            n=read();k=read();
            if(!n && !k)return 0;
            for(int i=1;i<=n;i++) scanf("%lf",&t[i].a);
            for(int i=1;i<=n;i++) scanf("%lf",&t[i].b);
            l=0,r=1e10;
            while(fabs(r-l)>eps) {
                mid=(l+r)/2.0;
                if(check(mid)) l=mid;
                else r=mid;
            }
            printf("%.0lf
    ",(l*100.0));
        }
    }
    

    例:Talent Show G

    01背包+分数规划,01背包价值就是 d ,体积是 w ,最后看f[W] 是不是大于等于0就行

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    using namespace std;
    int n,m;
    int w[300],t[300];
    long long f[10000];
    bool check(int z){
    	memset(f,128,sizeof(f));
        f[0]=0;
    	long long tmp=f[m];
    	for(int i=1;i<=n;i++)
    		for(int j=m;j>=0;j--) {
                int jj=min(m,j+w[i]);
                f[jj]=max(f[jj],f[j]+t[i]-(long long)w[i]*z);
            }
    	return f[m]>=0;
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++) {
    		scanf("%d%d",&w[i],&t[i]);
    		t[i]*=1000;
    	}
        int l=0,r=1000000,ans;
    	while(l<=r){
    		int mid=l+r>>1;
    		if(check(mid)) ans=mid,l=mid+1;
    		else r=mid-1;
    	}
    	printf("%d",ans);
    	return 0;
    }
    

    最优比率生成树

    Poj2728Desert King

    依然二分,每条边的 a[i]-mid×b[i] 视作权值。

    因为要求最小,直接求最小生成树即可。判断一下最小生成树的权值和

    这道题是完全图,所以我们需要用prim求最小生成树

    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return f*x;
    }
    const int N = 1006; 
    const int inf = 0x3f3f3f3f;
    const double eps = 1e-6;
    int n;
    double cost[N][N],val[N][N],c[N][N],mi[N];
    int x[N],y[N],z[N];
    inline double get(int i,int j) {
        return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
    }
    bool vis[N];
    bool check(double mid) {
        for(int i=1;i<=n;i++) 
            for(int j=i;j<=n;j++) 
                if(i==j) c[i][j]=c[j][i]=inf;
                else c[i][j]=c[j][i]=cost[i][j]-mid*val[i][j];
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=n;i++) mi[i]=inf;
        mi[1]=0;
        double ans=0;
        while(1) {
            int x=0;
            for(int i=1;i<=n;i++)//每次选出最小的边权,共n-1条边,第一次是零为了更新边权
                if(!vis[i]&&(!x||mi[x]>mi[i])) 
                    x=i;
            if(!x) break;
            vis[x]=1;
            ans+=mi[x];
            for(int i=1;i<=n;i++) mi[i]=min(mi[i],c[x][i]);
        }
        return ans>=0;
    }
    int main() {
        while(1) {
            n=read();
            if(!n) return 0;
            for(int i=1;i<=n;i++) 
                x[i]=read(),y[i]=read(),z[i]=read();
            double l=0,r=0,mid;
            for(int i=1;i<=n;i++)
                for(int j=i+1;j<=n;j++) {
                    r+=(cost[i][j]=cost[j][i]=abs(z[i]-z[j]));
                    val[i][j]=val[j][i]=get(i,j);
                }
            while((r-l)>=eps) {
                mid=(l+r)/2.0;
                if(check(mid)) l=mid;
                else r=mid;
            }
            printf("%.3lf
    ",l);
        }
    }
    

    最优比率生成环

    Sightseeing Cows G

    Poj3621

    Description

    给定一张图,边上有花费,点上有收益,点可以多 次经过,但是收益不叠加,边也可以多次经过,但是费用 叠加。求一个环使得收益和/花费和最大,输出这个比值。

    Solution

    比上面更加的恶心了。先不说环的问题,就是花费和收益不在一处也令人蛋疼。这时候需要用到几个转化和 结论。
    首先的一个结论就是,不会存在环套环的问题,即最优的方 案一定是一个单独的环,而不是大环套着小环的形式。这个的 证明其实非常的简单(提示,将大环上 的收益和记为x1,花费为y1,小环上的为x2,y2。重叠部分的花费为S。表示出来分类讨论即可)。有了这个结论,我们就可以 将花费和收益都转移到边上来了,因为答案最终一定是一个环, 所以我们将每一条边的收益规定为其终点的收益,这样一个环 上所有的花费和收益都能够被正确的统计。
    解决了这个问题之后,就是01分数规划的部分了,我们只需要计算出D数组后找找有没有正权环即可,不过这样不太好, 不是我们熟悉的问题,将D数组全部取反之后,问题转换为查找有没有负权环,用spfa即可。

    还有一些细节证明必然不会有一个点被经过两次

    #include <queue>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    inline int read() {
    	int x=0;char ch=getchar();
    	while(!isdigit(ch)) ch=getchar();
    	while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
    	return x;
    }
    const double eps=1e-5;
    const int M=10005;
    const int N=2005;
    int n,m;
    int fun[N];
    struct node{
    	int to,w,nxt;
    	double d;
    }e[M];
    int hd[M],tot;
    inline void add(int x,int y,int z) {
    	e[++tot].to=y;e[tot].w=z;e[tot].nxt=hd[x];hd[x]=tot;
    }
    queue<int>q;
    bool vis[N];
    int cnt[N];
    double dis[N];
    bool check(double mid) {
    	for(int i=1;i<=tot;i++)
    		e[i].d=(double)mid*e[i].w-fun[e[i].to];
    	while(q.size()) q.pop();
    	for(int i=1;i<=n;++i){//因为图不一定连通,所以初始所有结点入队
        	q.push(i);
        	dis[i]=0; vis[i]=cnt[i]=1;
        }
    	while(!q.empty()) {
    		int x=q.front();q.pop();
    		vis[x]=0;
    		for(int i=hd[x];i;i=e[i].nxt) {
    			int y=e[i].to;
    			if(dis[y]>dis[x]+e[i].d){
    				dis[y]=dis[x]+e[i].d;
    				if(!vis[y]) {vis[y]=1,q.push(y);if(++cnt[y]>=n) return 1;}
    			}
    		}
    	}
    	return 0;
    }
    
    int main() {
    	n=read();m=read();
    	for(int i=1;i<=n;i++) fun[i]=read();
    	for(int i=1,x,y,z;i<=m;i++) {
    		x=read();y=read();z=read();
    		add(x,y,z);//有向图
    	}
    	double l=0,r=100000,mid;
    	while((r-l)>eps) {
    		mid=(l+r)/2.0;
    		if(check(mid)) l=mid;
    		else r=mid;
    	}
    	printf("%.2lf",l);
    	return 0;
    }
    

    天路

    板子题but ----T飞了好几次

    if((cnt[y]=cnt[x]+1)>=n) return 1;要写成这样

    而不是这样 if(++cnt[y]>=n) return 1;

    #include <queue>
    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    inline int read() {
    	int x=0;char ch=getchar();
    	while(!isdigit(ch)) ch=getchar();
    	while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
    	return x;
    }
    const double eps=1e-5;
    const int M=40005;
    const int N=7005;
    int n,m;
    int fun[N];
    struct node{
    	int to,nxt,v,p;
    	double d;
    }e[M];
    int hd[M],tot;
    inline void add(int x,int y,int v,int p) {
    	e[++tot].to=y;e[tot].v=v;e[tot].p=p;e[tot].nxt=hd[x];hd[x]=tot;
    }
    bool vis[N];
    int cnt[N];
    double dis[N];
    bool check(double mid) {
    	queue<int>q;
    	for(int i=1;i<=n;++i){//因为图不一定连通,所以初始所有结点入队
        	q.push(i);
        	dis[i]=0; vis[i]=cnt[i]=1;
        }
    	while(!q.empty()) {
    		int x=q.front();q.pop();
    		vis[x]=0;
    		for(int i=hd[x];i;i=e[i].nxt) {
    			int y=e[i].to;
    			if(dis[y]>dis[x]+(double)mid*e[i].p-e[i].v){
    				dis[y]=dis[x]+(double)mid*e[i].p-e[i].v;
    				if(!vis[y]) {vis[y]=1,q.push(y);if((cnt[y]=cnt[x]+1)>=n) return 1;}
    			}
    		}
    	}
    	return 0;
    }
    
    int main() {
    	n=read();m=read();
    	for(int i=1,x,y,v,p;i<=m;i++) {
    		x=read();y=read();v=read();p=read();
    		add(x,y,v,p);//有向图
    	}
    	double l=0,r=200,mid;
    	bool f=0;
    	while(abs(r-l)>eps) {
    		mid=(l+r)/2.0;
    		if(check(mid)) f=1,l=mid;
    		else r=mid;
    	}
    	if(!f) puts("-1");
    	else printf("%.1lf",l);
    	return 0;
    }
    

    各种例题:

    规划

    树型背包,求树上一个n - m的连通块,使块中的点的权值之和最大。

    (dp[u][j])表示以u为根的子树中,有一个点数为 j 的联通块时的最大值。这个联通块一定是包含u的。

    动态转移方程:$$ f[u][j]=Max(f[u][j],f[u][j-k]+f[v][k]); $$

    u是当前节点,v是儿子,j和k是要自己枚举的

    #include <queue>
    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    inline int read() {
    	int x=0;char ch=getchar();
    	while(!isdigit(ch)) ch=getchar();
    	while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
    	return x;
    }
    const double eps=1e-5;
    const int M=1000;
    const int N=1000;
    int n,m;
    int a[N],b[N];
    struct node{
    	int to,nxt;
    	double d;
    }e[M];
    int hd[M],tot;
    inline void add(int x,int y) {
    	e[++tot].to=y;e[tot].nxt=hd[x];hd[x]=tot;
    }
    bool vis[N];
    int cnt[N];
    double d[N],f[N][N];
    inline void dfs(int x,int fa) {
    	f[x][0]=0;
    	for(int i=hd[x];i;i=e[i].nxt) {
    		int y=e[i].to;
    		if(y==fa) continue;
    		dfs(y,x);
    		for(int j=m-1;j>=0;j--)
    			for(int k=1;k<=j;k++)
    				f[x][j]=max(f[x][j],f[x][j-k]+f[y][k]);
    	}
    	for(int i=m;i>=1;i--) f[x][i]=f[x][i-1]+d[x];//必须选自己,所以先求出m-1个物品的,再加上自己的
    	f[x][0]=0;
    }
    
    inline bool check(double mid) {
    	memset(f,0xcf,sizeof(f));
    	for(int i=1;i<=n;i++) d[i]=(double)a[i]-mid*b[i];
    	dfs(1,0);
    	for(int i=1;i<=n;i++) 
    		if(f[i][m]>-eps) return 1;
    	return 0;
    }
    
    int main() {
    	n=read();m=read();
    	m=n-m;
    	for(int i=1;i<=n;i++) a[i]=read();
    	for(int i=1;i<=n;i++) b[i]=read();
    	for(int i=1,x,y;i<n;i++) {
    		x=read();y=read();
    		add(x,y);add(y,x);
    	}
    	double l=0,r=1000000,mid;
    	while(r-l>eps) {
    		mid=(l+r)/2.0;
    		if(check(mid)) l=mid;
    		else r=mid;
    	}
    	printf("%.1lf",l);
    	return 0;
    }
    

    https://www.luogu.com.cn/problem/P1730

    Poj3155——Hard Life最大密度子图

    Zoj2676——Network Wars

    游戏——最大密度子图变种

    Poj3266——CowSchool0/1分数规划+数据结构

    【P6087,JSOI2015】送礼物

    【BZOJ3232】圈地游戏(难)

    https://www.luogu.com.cn/problem/P4322

  • 相关阅读:
    一秒解决element容器布局显示内部调节框的问题
    操作管理员
    Linux_网络基础管理
    如何解决在WordPress安装Redis插件时需要输入FTP问题?
    如何在我的EC2实例状态更改时获取自定义电子邮件通知
    利用S3fs在Amazon EC2 Linux实例上挂载S3存储桶
    源码安装Apache(httpd)
    Linux_源码安装包管理理论概述
    Linux_yum命令详解
    Linux_yum仓库管理
  • 原文地址:https://www.cnblogs.com/ke-xin/p/13544425.html
Copyright © 2011-2022 走看看