贪心算法 & 动态规划基础题

[TOC]

acm

标签(空格分隔): acm

贪心算法

//头文件set map
#include<set>
#include<map>
//greater头文件
#include<functional>
//按从大->小排序
multiset<int,greater<int>> bset;
multimap<int,int,greater<int>> dbmap;

//查找第一个大于等于指定值得迭代器
multimap<int,int,greater<int>> dbmap;
class map_finder 
{ 
public: 
    map_finder(const int &v):value(v){} 
    bool operator ()(const multimap<int,int,greater<int>>::value_type &pair) 
    { 
        return pair.first >= value; 
    } 
private: 
    const int value;                     
}; 
multimap<int,int>::iterator it=find_if(dbmap.begin(),dbmap.end(),map_finder(d));
//注意查找的迭代器和rbegin() rend()

51Nod 1191消灭兔子

典型的贪心,每个兔子只能射一次,所以只能用伤害值大于其血量的箭,在此前提下,箭越便宜

越好,故对兔子血量升序排列,箭对伤害值升序排列。
若i小于j 则第i支箭可以杀死的兔子,第j支箭也一定能杀死,而若j的价格小于i,就应该用j,
所以用优先数列维护即可。

#include<cstdio>
#include<cstring>
#include<cstdlib>
//#define LOCAL
#include<cmath>
#include<iostream>
#include<algorithm>
#include<queue>
#include<functional>
#include<vector>
using namespace std;
const int N = 50009;
pair<int, int> p[N];
int b[N];
int main()
{
#ifdef LOCAL
    freopen("1191.in","r",stdin);
    freopen("1191.out","w",stdout);
#endif
    int n,m;
    scanf("%d%d",&n,&m);
    if(m < n)
    {
        printf("No Solution\n");
        return 0;
    }
    for(int i=0;i<n;i++)
        scanf("%d",&b[i]);
    for(int i=0;i<m;i++)
        scanf("%d%d",&p[i].first, &p[i].second);
    sort(b, b+n);
    sort(p, p+m);
    priority_queue<int> q;
    
    int i = 0, j = 0;
    long long ans = 0;
    //此处经典,先把p[j].first加入优先级队列,通过比较,看p[j]能否杀死第[i]只兔子 
    //如果不能杀死,就从优先级队列中去掉一个优先级最高的(注意p[j]能杀死第i只以前
    //的所有兔子),注意此时该值不一定等于
    //p[j].fisrt
    while(j < m)
    {
        ans += p[j].second;
        q.push(p[j].second);//价值越大,优先级越高,越出的快 
        if(p[j].first >= b[i] && i < n)
        {
            ++i;++j;
        }
        else
        {
            ans -= q.top();
            q.pop();
            ++j;
        }
    }
    if(i < n)
        printf("No Solution\n");
    else
        printf("%lld\n",ans);
    return 0;
} 

51 Nod 1065 最小正子段和

参考
思路:利用前缀和,注意如果使用tmp = p[i].first – p[i-1].first 结果错误,原因不知

#define LOCAL 
#include<iostream>
#include<algorithm>
#include<functional>
using namespace std;
const int N = 50009;
pair<long long, int> p[N];
int main()
{
#ifdef LOCAL
    freopen("1065.in","r",stdin);
    freopen("1065.out","w",stdout);
#endif
    int N;
    long long ans=0,llv;
    p[0].first = 0 ;
    p[0].second = 0 ;
    cin >> N ;
    for(int i = 1;i <= N;i++)
    {
        cin>>llv;
        ans = ans + llv ;
        p[i].first = ans ;
        p[i].second = i ;
    }
    sort(p,p+N+1);
    
    long long tmp;
    int flag = 0;
    for(int i = 1 ; i <= N ; i++)
    {
        if((p[i].first>p[i-1].first>0)&&(p[i].second>p[i-1].second))
        {
            if(flag == 0)
            {
                flag = 1 ;
                ans = p[i].first-p[i-1].first;
            }
            else
            {
                if(p[i].first-p[i-1].first < ans)
                    ans = p[i].first-p[i-1].first ;
            }   
        }   
    }
    cout << ans << endl; 
    return 0;
} 

51Nod1449 砝码称重

现在有好多种砝码,他们的重量是 w0,w1,w2,… 每种各一个。问用这些砝码能不能表示一个重量为m的东西。
样例解释:可以将重物和3放到一个托盘中,9和1放到另外一个托盘中。

思路:转换成w进制,m能否表示为w进制的数:
$$m=k0w^0+k1w1+k2*w2+k3w^3+….+knw^n$$
其中k0 ……. kn (k=-1,0,1) ,则w能被表示上述形式。

关于求余数的运算:
根据取余数的定义,如果m和n是整数且n非0,则表达式(m/n)*n+m%n的求值结果与m相等。隐含的意思是,如果m%n不等于0,则它的符号和m相同。C++语言的早期版本中允许m%n的符号匹配n的符号,而且商项负无穷一侧取整,这一方式在新标准中已经被禁止使用了。除了-m导致溢出的特殊情况,其他时候(-m)/n和m/(-n)都等于-(m/n),m&(-n)等于(-m)%n等于-(m%n)

#include <iostream>  
using namespace std;
int w, m;
int main()
{
    scanf("%d%d", &w, &m);
    while (m)
    {
        if (m%w == 1 || m%w == 0)
        {
            m = m / w;
        }
        else if(m%w==w-1)
        {
            m = m / w + 1;
        }
        else
        {
            puts("NO");
            return 0;
        }
    }
    puts("YES");
    return 0;
} 

51Nod 1288汽油补给

  • 这个题目一开始一点思路都没有,后来想每次到加油站都将油箱试图加满,如果里面的油都是比自己便宜的,不加。每次都替换油箱中比自己贵的油,然后跑完了一段再算之前的钱。即油先跑着,钱后算账。
    把油箱里面的油按价钱分成相应的部分。每段路程车都是满油启动。
#pragma warning(disable:4996)
#include <iostream>
#include <algorithm>
#include <cmath>
#include <vector>
#include <string>
#include <cstring>
#include <set>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;

struct no
{
    ll price;
    ll num;

    bool operator<(const no & n1)
    {
        return price < n1.price;
    }
};
const int maxn = 100005;
ll n, t;
ll dis[maxn], pri[maxn];

void input()
{
    int i, j, k;
    scanf("%lld%lld", &n, &t);

    for (i = 1; i <= n; i++)
    {
        scanf("%lld%lld", &dis[i], &pri[i - 1]);
    }
}

void solve()
{
    int i, j, k;
    vector<no>qu;
    int flag = 1;
    ll res = 0;
    
    for (i = 0; i <= n; i++)
    {
        if (t < dis[i + 1])flag = 0;
        if (flag == 0)continue;

        if (i == 0)
        {
            no n1;
            n1.price = pri[0];
            n1.num = t;
            qu.push_back(n1);
        }
        else
        {
            sort(qu.begin(), qu.end());
            ll di = 0;
            ll rmin = 0;

            while (true)
            {
                if (di + qu[0].num >= dis[i])
                {
                    rmin = rmin + (dis[i] - di)*(qu[0].price);
                    if (di + qu[0].num >= dis[i])
                    {
                        qu[0].num = qu[0].num - (dis[i] - di);
                    }
                    if (qu[0].num == 0)
                    {
                        qu.erase(qu.begin());
                    }
                    break;
                }
                else
                {
                    di = di + qu[0].num;
                    rmin += qu[0].num*qu[0].price;
                    qu.erase(qu.begin());
                }
            }
            res += rmin;
            
            ll sum = 0;
            for (k = 0; k < qu.size(); k++)
            {
                if (pri[i] > qu[k].price)
                {
                    sum += qu[k].num;
                }
                else
                {
                    no n1;
                    n1.num = t - sum;
                    n1.price = pri[i];
                    qu.erase(qu.begin() + k, qu.end());
                    qu.push_back(n1);
                    sum = t;
                    break;
                }
            }
            if (sum < t)
            {
                no n1;
                n1.price = pri[i];
                n1.num = t - sum;
                qu.push_back(n1);
            }
        }
    }
    if (flag == 0)
    {
        puts("-1");
    }
    else
    {
        printf("%lld", res);
    }
}

int main() 
{
//  freopen("i.txt","r",stdin);
//  freopen("o.txt","w",stdout);

    input();
    solve();

    return 0;
}

动态规划

51Nod 1049 最大子段和

endmax=answer=A[0];
for(i=1;i<N;i++)
{
    endmax=max(endmax,0LL)+A[i];
    answer=max(answer,endmax);
}
  • 说明:假设现在遍历到i处:endmax表示前i-1项的最大字段和,如果前i-1项的字段和为负数,那就抛弃前i-1项,从第i项开始算。(因为对最大字段和来说,加上一个负数,还不如不加,从当前开始算)
#define LOCAL
#include<iostream>
#include<cstring>
#include<algorithm>
#include<climits>
const int maxn=50005;
using namespace std;
int main(void)
{
#ifndef LOCAL
    freopen("data2.in","r",stdin);
    freopen("data2.out","w",stdout);
#endif
    long long A[maxn],N,i;
    long long endmax,answer;
    cin>>N;
    for(i=0;i<N;i++)
    {
        cin>>A[i];
    }
    endmax=answer=A[0];
    for(i=1;i<N;i++)
    {
        endmax=max(endmax,0LL)+A[i];
        answer=max(answer,endmax);
    }
    cout<<answer<<endl; 
    return 0;
}

51Nod最大字段和

//#define LOCAL
#include<iostream>
const int maxn=505;
const int maxm=505;
using namespace std;
int main(void)
{
#ifdef LOCAL
    freopen("最大子矩阵和.in","r",stdin);
    freopen("最大子矩阵和.out","w",stdout);
#endif
    int M,N;
    long long v[maxn][maxm],c[maxn],endmax,answer,ans;
    int i,j,k;
    cin >> M >> N ;
    for(i = 1 ;i <= N ; i ++)
        for(j = 1 ; j <= M ; j ++)
            cin >> v[i][j] ;
            
    c[0] = v[1][1] ;
    ans = v[1][1];
    for(i = 1 ; i <= M ; i ++ )
    {
        for(j = i ; j <= M ; j++)
        {
            //处理第j列 
            for(k = 1 ; k <= N ; k ++)
            {
                c[k] = (j == i) ? v[k][j] : (c[k] + v[k][j]);   
            }
            // 动态规划,求最大字段和
            endmax = answer = c[1] ;
            for (k = 2 ; k <= N ; k ++)
            {
                endmax = max (endmax,0ll) + c[k];
                answer = max (answer,endmax);
            }
            if (ans < answer)
                ans = answer ;  
        }
    }
    cout << ans << endl ;
    return 0;
}

51Nod 1050 循环数组最大子段和

解释
非常灵活的思想

#define LOCAL
#include<iostream>
const int maxn=50005;
using namespace std;
int main(void)
{
#ifdef LOCAL
    freopen("循环数组最大字段和.in","r",stdin);
    freopen("循环数组最大字段和.out","w",stdout);
#endif
    int N;
    long long s[maxn]; 
    long long endmin,ansmin,endmax,ansmax,sum;
    int i;
    cin >> N ;
    sum = 0 ;
    for(i = 0 ;i < N ; i ++)
    {
        cin >> s[i] ;
        sum += s[i] ;
    }
    endmin = ansmin = s[0];
    endmax = ansmax = s[0];
    for(i=1;i<N;i++)
    {
        endmin = min(endmin,0LL) + s[i];
        ansmin = min(ansmin,endmin);
        
        endmax = max(endmax,0LL) + s[i];
        ansmax = max(ansmax,endmax); 
    }
    cout << max(ansmax,sum-ansmin) << endl;
                
    return 0;
}

51Nod 1086背包问题

思路:转换成二进制,第i件物品有c[i]个,每件体积为w[i],价值为p[i]
$$c[i]=k02^0+k121+k2*22+k32^3+….+kn2^n$$
其中k0 ……. kn (k=0,1) ,则 c[i]能被表示上述形式。

for(k = 1 ; k <= c[i] ;k *= 2)
    {
        tmpw = k * w[i] ;
        tmpp = k * p[i] ;
        for(j = W;j >= tmpw ; j--)
        {
            f[j]=max(f[j],f[j-tmpw]+tmpp);
        }
        c[i] = c[i] - k ;
    }
    for(k = W ; k >= w[i] * c[i] ; k --)
        f[k] = max(f[k],f[k-w[i] * c[i]] + p[i] * c[i]);
#define LOCAL
#include<iostream>
#include<cstring>
const int MAXN=101;
const int MAXW=50001;
using namespace std;
int main(void)
{
#ifdef LOCAL
    freopen("多重背包问题.in","r",stdin);
    freopen("多重背包问题.out","w",stdout);
#endif
    int N, i, j, W;
    int f[MAXW];
    int w[MAXN],p[MAXN],c[MAXN];//体积,价值,数量 
    int tmpw,tmpp;
    cin >> N >> W;
    for(i = 1 ; i <= N ; i ++)
    {
        cin >> w[i] >> p[i] >> c[i];
    }
    memset(f,0,sizeof(f));
    int k ;
    for(i = 1 ; i <= N ; i++)
    {
        for(k = 1 ; k <= c[i] ;k *= 2)
        {
            tmpw = k * w[i] ;
            tmpp = k * p[i] ;
            for(j = W;j >= tmpw ; j--)
            {
                f[j]=max(f[j],f[j-tmpw]+tmpp);
            }
            c[i] = c[i] - k ;
        }
        for(k = W ; k >= w[i] * c[i] ; k --)
            f[k] = max(f[k],f[k-w[i] * c[i]] + p[i] * c[i]);
    }
    cout << f[W] << endl ; 
    return 0;
}
    原文作者:百炼
    原文地址: https://www.jianshu.com/p/d543bbbaf321
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞