贪心算法

一、基本概念:

     所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。

     贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。

    所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。

 

二、贪心算法的基本思路:

    1.建立数学模型来描述问题。

    2.把求解的问题分成若干个子问题。

    3.对每一子问题求解,得到子问题的局部最优解。

    4.把子问题的解局部最优解合成原来解问题的一个解。

 

三、贪心算法适用的问题

      贪心策略适用的前提是:局部最优策略能导致产生全局最优解。

    实际上,贪心算法适用的情况很少。一般,对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可做出判断。

 

四、贪心算法的实现框架

    从问题的某一初始解出发;

    while (能朝给定总目标前进一步)

    { 

          利用可行的决策,求出可行解的一个解元素;

    }

    由所有解元素组合成问题的一个可行解;

  

五、贪心策略的选择

     因为用贪心算法只能通过解局部最优解的策略来达到全局最优解,因此,一定要注意判断问题是否适合采用贪心算法策略,找到的解是否一定是问题的最优解。

1.最大值最小化 


题目的意思就是给出m个数,分成k组,只能连续的分成一组。求分组中和的最大值最小。 
思路:二分枚举最大值,然后划分的每一组尽量接近这个枚举的值,最大值尽量小。 
这道题的核心部分是二分枚举,贪心主要体现在尽量靠近枚举的值
 

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
const int maxn = 505;
long long l, r, n, group;
int a[maxn];
int vis[maxn];
bool judge(long long x) {

    int k = 1, i;
    long long sum = 0;
    for(i=0; i<n; i++) {

        if(sum + a[i] <= x) {

            sum += a[i];
        }else {

            k++;
            sum = a[i];
        }
        if(k>group) return false;
    }
    return true;
}

void print() {


    memset(vis, 0, sizeof(vis));
    int k = 1,i;
    long long sum=0;
    for(i=n-1; i>=0; i--) {

        if(sum+a[i]<=l) {
            sum += a[i];
        }else {

            k++;
            sum = a[i];
            vis[i] = 1;
        }
    }
    for(i=0; i<n-1&&k<group; i++) {

        if(!vis[i]) {

            vis[i]=1;
            k++;
        }
    }
    for(i=0; i<n; i++) {

        if(i!=n-1) {

            printf("%d ", a[i]);
            if(vis[i]) {

                printf("/ ");

            }
        }
        else printf("%d\n", a[i]);
    }
}
int main() {

    int kase;
    scanf("%d", &kase);
    while(kase--) {

        long long sum = 0;
        long long m = 0;
        int i;
        scanf("%d%d", &n, &group);
        for(i=0; i<n; i++) {

            scanf("%d", &a[i]);
            sum += a[i];
            m = a[i]>m?a[i]:m;
        }
        l=m, r=sum;
        while(r>l) {
            long long mid = (l+r)/2;
            if(judge(mid)) {
                r=mid;
            }else l=mid+1;
        }
        print();

    }
}


2.尽量少的资源消耗 


有一块草坪,长为l,宽为w,在它的水平中心线上有n个位置可以安装喷水装置,各个位置上的喷水装置的覆盖范围为以它们自己的半径ri为圆。求出最少需要的喷水装置个数。 
区间覆盖问题 
代码:

#include<cstdio>
#include<iostream>
#include<string>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 10005;
int n;
double l, w;
double p, r;
int num;
double cal() {
    return sqrt(r*r-w*w/4.0);
}
struct node {
    double le, ri;
    bool operator<(const node& c)const {
        return le<c.le;
    }
}arr[maxn];
int main() {
    while(scanf("%d%lf%lf", &n, &l, &w) != EOF) {
        num=0;
        for(int i=0; i<n; i++) {
            scanf("%lf%lf", &p, &r);
            if(r<=w/2) continue;
            double t=cal();
            arr[num].le=p-t;
            arr[num].ri=p+t;
            num++;
        }
        double le=0, ri=0;
        int flag=0, ans=0;
        sort(arr,arr+num);
        if(arr[0].le<=0) {
            int i=0;
            while(i<num) {
                int j=i;
                while(j<num && arr[j].le<=le) {
                    if(ri<arr[j].ri) ri=arr[j].ri;
                        j++;
                }
                if(i==j) break;
                le = ri;
                i=j;
                ans++;
                if(ri>=l) 
                {
                    flag = 1;
                    break;
                }
            }
        }
        if(flag) printf("%d\n", ans);
        else printf("-1\n");

    }
    return 0;
}



3.多机调度问题 


公司要你要完成N份任务,但是你是不可能全部完成的,所以需要雇佣别人来做,做到剩下M份时,自己再亲自出马。现在有个机构,有两种付费方式,第一种是每付A元帮你完成1份,第二种是每付B元帮你完成剩下任务的一半。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 105;
char str[maxn];
int n, m, l;
struct node {

    char name[maxn];
    int half;
    int per;
    int money;
}p[maxn];

void deal (int j) {

    int k = 0,i;
    for (i = 0; i < strlen (str); i++) {

        if (str[i] != ':')
            k++;
        else
            break;  
    }
    memcpy (p[j].name, str, sizeof (str));
    p[j].name[k] = '\0';

    int sum = 0;
    for (i = k + 1; i < strlen (str); i++) {

        if (str[i] != ',')
            sum = sum * 10 + str[i] - '0';
        else {

            p[j].per = sum;
            sum = 0;
        }
    }
    p[j].half = sum;   
}

void cal(int cur) {

    int num = n;
    int sum = 0;
    while(num/2>=m && (p[cur].per*(num-num/2))>=p[cur].half) {
        sum += p[cur].half;
        num/=2;
    }
    sum += (num-m)*p[cur].per;
    p[cur].money = sum;
}


int cmp (const node & x, const node & y) {

    if (x.money < y.money)
        return true;
    else if (x.money > y.money)
        return false;
    else return strcmp (x.name, y.name) < 0 ? true: false;
}
int main() {

    int kase;
    int cas = 1;
    scanf("%d", &kase);
    int i, j;
    while(kase--) {
        printf("Case %d\n", cas++);
        scanf("%d%d%d", &n, &m, &l);
        getchar();
        for(i=0; i<l; i++) {
            gets(str);
            deal(i);
            cal(i);

        }
        sort(p, p+l, cmp);
        for (j = 0; j < l ; j++)
            printf ("%s %d\n", p[j].name, p[j].money);

    }

    return 0;
}


4.最小生成树 


题目:把所有的点连起来最短 
1. Kruskal

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath> 
using namespace std;
const int N = 105;
double x[N], y[N];
struct edge {
    double x, y, d;
}e[N*N];
int f[N];
double ans;
double dis(double x1, double y1, double x2, double y2) {
    return sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
}
int n;
int num;
int cmp(edge a, edge b) {
    return a.d < b.d;
}
int find(int x) {
    return (x == f[x])?x:find(f[x]);
}
void uni(int a, int b) {

    f[a] = find(b);
}

void ki() {
    ans = 0;
    for(int i=0; i<n; i++) f[i] = i;
    for(int i=0; i<num; i++) {
        int a = find(e[i].x);
        int b = find(e[i].y);
        if(a != b) {
            uni(a,b);
            ans += e[i].d;
        }
    }
    printf("%.2lf\n", ans);
}

int main() {
    int cas; 
    scanf("%d", &cas);
    while(cas--) {
        num = 0;
        scanf("%d", &n);
        for(int i=0; i<n; i++) {
            scanf("%lf%lf", &x[i], &y[i]);
        }
        for(int i=0; i<n; i++)
            for(int j=0; j<n; j++) {
                if(i != j) {
                e[num].x = i;
                e[num].y = j;
                e[num++].d = dis(x[i], y[i], x[j], y[j]);
                }
            }
        sort(e, e+num, cmp);
        ki();
        if(cas) printf("\n");
    }

    return 0;
}
#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int inf = 0xFFFFFFF;
double x[105], y[105], g[105][105];
int vis[105];
double d[105];
double ans;
int n;
double dis(double x1, double y1, double x2, double y2) {
    return sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
}
void prim() {
    for(int i=0; i<n; i++) d[i] = g[0][i];
    d[0] = 0;
    memset(vis, 0, sizeof(vis));
    double m;
    int x = 0;

    for(int i=0; i<n; i++) {
        m = inf;
        for(int j=0; j<n; j++) {
            if(d[j]<m && !vis[j]) {
                m = d[j];
                x = j;
            }
        }
        if(m==inf) break;
        ans += m;
        vis[x] = 1;
        for(int j=0; j<n; j++) {
            if(d[j]>g[j][x] && !vis[j]) d[j] = g[j][x];//集外任意一点到已经纳入集合内的点在重调一次距离
        }
    }
}

int main() {
    int cas;
    scanf("%d", &cas);
    while(cas--) {
        memset(g, 0, sizeof(g));
        scanf("%d", &n);
        for(int i=0; i<n; i++) {
            scanf("%lf %lf", &x[i], &y[i]);
        }
        for(int i=0; i<n; i++)
            for(int j=0; j<n; j++) {
                g[i][j] = dis(x[i], y[i], x[j], y[j]);
            }
        ans = 0;
        prim();
        printf("%.2lf\n", ans);
        if(cas) printf("\n");
    }
    return 0;
}


5.最短路径

#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
int n, m, s, d;
const int maxn = 200005;
const int inf = 0x3f3f3f3f;
struct node {
    int n,w;
    node(){}
    node(int a,int b) {
        n=a;
        w=b;
    }
    bool operator<(const node& q)const {

        return w>q.w;
    }
};
vector<node>v[maxn];
int dis[maxn];
void dijkstra() {
    int i;
    int ans = 0;
    dis[s] = 0;
    priority_queue<node>q;
    q.push(node(s,0));
    while(!q.empty()) {

        node u = q.top();
        q.pop();
        for(i=0; i<v[u.n].size(); i++) {
            node temp = v[u.n][i];//遍历每个点
            if(dis[temp.n]>dis[u.n]+temp.w) {
                dis[temp.n] = dis[u.n]+temp.w;//w是从u.n到temp.n的权重
                q.push(temp);
            }
        }

    }
}
int main() {
    int kase;
    int t=1;
    int i,j;
    int a,b,c;
    scanf("%d", &kase);
    while(kase--) {
        scanf("%d%d%d%d", &n,&m,&s,&d);
        for(i=0; i<=n; i++) v[i].clear();
        memset(dis, inf, sizeof(dis));
        for(i=0; i<m; i++) {

            scanf("%d%d%d", &a,&b,&c);
            v[a].push_back(node(b,c));
            v[b].push_back(node(a,c));

        }
        dijkstra();
        printf("Case #%d: ", t++);  
        if (dis[d] < inf)  
            printf("%d\n", dis[d]);  
        else  
            printf("unreachable\n");  
    }
    return 0;
}