再读《挑战程序设计竞赛》——初出茅庐(2)

一往直前!贪心法

贪心就是套方案或者数学证明呗,题做多了就有感觉了。
贪心法的证明大致有3种:

  • 反证法
  • 数学归纳(比如,先列举一个最简单的情况,如果有大小关系,我们可以交换两个项,把结果的大小进行比较)
  • A>=B且A<=B则A=B.我们一般把A当做我们的贪心解法,B当做最优解,显然A<=B,我们只需要证明A>=B即可。

贪心中真的很多排序,堆,pair,结构体,要想清楚他们维护的是什么,考验的是基础的代码实现逻辑和双指针算法等。还有很多边界问题,比如前导0,相等,奇偶数…

书上的题:
区间调度问题:
这个我们都知道,选结束时间最早的嘛。
因为是对结束区间排序,我们把结束端点放进pair的first里,开始端点放在pair的second里。

pair<int,int> p[N];
void solve()
{
	sort(p,p+n);
	int t=p[0].first;
	for(int i=1;i<n;i++)
	{
		if(p[i].second>t)
		{
			p[i].first=t;
			ans++;
		}
	}
}

字典序最小问题
这题重要在对于一些特殊值,比如边界,相等的情况的处理。
这题的贪心策略是将S与S翻转过的字符串比较(不是真翻转,就是用指针倒着数即可),因为当头字符和尾字符相等,我们必须比较去掉他们后的下一个字符再做定论。

void solve()
{
	int i=0,j=n-1;
	while(i<=j)
	{
		bool flag=0;
		for(int k=0;k<n;k++)
		{
			if(s[a+k]>s[b-k])
			{
				flag=0;
				break;
			}
			if(a[a+k]<s[b-k])
			{
				flag=1;
				break;
			}
		}
	}
	if(flag)
	{
		cout<<s[b];
		b--;
	}
	else
	{
		cout<<s[a];
		a++;
	}
} 

Saruman’s Army
这个策略也很简单,我们先找到能覆盖当前点的最小点,再找到这个点能覆盖到的最右边的点。如此循环。

void solve()
{
	sort(a,a+n);
	int i=0,ans=0;
	while(i<n) 
	{
		int s=a[i];
		i++;
		while(i<n&&a[i]<s+x)
			i++;
		i--;
		int p=a[i];
		while(i<n&&a[i]<=p+x) 
			i++;
		ans++;
	}
	cout<<ans;
} 

Fence Repair
这是一道经典的合并果子。。。
(合并果子等会会写到)

谈谈贪心的常用方式
贪心会与二分值的验证结合

洛谷的贪心题单(只做了提高组)
P2240部分背包问题
这根本不是个背包好吧,当然是先放单位重量价格最高的啦0v-
另外注意浮点数,不要直接比大小。

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
double w[N],v[N]; 
pair<double,int> a[N];
int main()
{
	int n;
	double t;
	cin>>n>>t;
	for(int i=0;i<n;i++)
	{
		cin>>w[i]>>v[i];
		a[i].first=w[i]/v[i];
		a[i].second=i;
	}
	double sum=0,res=0;
	sort(a,a+n);
	for(int i=0;i<n;i++)
	{
		if(w[a[i].second]-t<0.000001)
		{
			res+=v[a[i].second];
			t-=w[a[i].second];
		}
		else
		{
			res+=t/w[a[i].second]*v[a[i].second];
			break;
		}
	}
	printf("%.2f",res);
	return 0;
}

P1090合并果子

#include<bits/stdc++.h>
using namespace std;
const int N=10100;
int a[N];
priority_queue<int> q;
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
        q.push(-a[i]);
    }
    int res=0,x1,x2;
    while(q.size()!=1)
    {
        x1=q.top();
        q.pop();
        x2=q.top();
        q.pop();
        res=res-x1-x2;
        q.push(x1+x2);
    }
    cout<<res;
    
    return 0;
}

P1106删数问题
这道题想出贪心策略后还要考虑两点:

  1. 如果全删了,要输出0。
  2. 边界:如果最后遍历到i=n-k了,后面的直接全删掉。
#include<bits/stdc++.h>
using namespace std;
int a[500];
int main()
{
    string m;
    cin>>m;
    int k;
    cin>>k;
    int t=1;
    int flag=0,cnt=0;
    for(int i=1;i<=m.length();i++)
    {
        a[i]=m[i-1]-'0';
    }
    int rest=m.length()-k;
    while(cnt<rest)
    {
        int v=t;
        for(int i=t;i<=t+k;i++)
        {
            int x=a[v];
            if(a[i]<x)
            {
                v=i;
            }
        }
        if(a[v])
        {
            flag=1;
        }
        if(flag)
        {
            cout<<a[v];
        }
        k=k-(v-t);
        t=v+1;
        cnt++;
    }
    if(!flag)
        cout<<0;
    return 0;
}

P4995跳跳!
我是把他想成了一根直线,从一端跳到另一端可以用双指针。

#include<bits/stdc++.h>
using namespace std;
const int N=310;
long long h[310];
int main()
{
	int n;
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>h[i];
	}
	sort(h,h+n);
	long long res=0;
	if(h[n-1]>(-h[0])||h[0]>=0)
	{
		res+=h[n-1]*h[n-1];
		reverse(h,h+n);
	}
	else
	{
		res+=h[0]*h[0];
	}
	int i=0,j=n-1;
	
	if(n%2==0)
	{
	    while(i-j!=1)
    	{
    		res=res+(h[i]-h[j])*(h[i]-h[j]);
    		i++;
    		res=res+(h[i]-h[j])*(h[i]-h[j]);
    		j--;
    	}
	}
	else
	{
	    while(i!=j)
    	{
    		res=res+(h[i]-h[j])*(h[i]-h[j]);
    		i++;
    		res=res+(h[i]-h[j])*(h[i]-h[j]);
    		j--;
    	}
	}
	
	if(i==j-1)
	{
	    res+=(h[i]-h[j])*(h[i]-h[j]);
	}
	cout<<res;
	return 0;
} 

P4447 分组
可以暴力枚举每个数加在哪个后面,但是是O(n2)的
用堆维护最小值可以到O(nlogn)
对于每个序列,如果接不上了就可以从堆中丢弃,并取得长度最小值,如果接的上就接上去。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
priority_queue<pair<int,int> > pq;
int minn=INT_MAX;
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
    }
    sort(a,a+n);
    for(int i=0;i<n;i++)
    {
        if(pq.empty())
        {
            pq.push({-a[i],-1});
            continue;
        }
        while(!pq.empty())
        {
            if(a[i]>1-pq.top().first)
            {
                //cout<<a[i]<<" "<<1-pq.top().first<<pq.top().second<<endl;
                //cout<<pq.size()<<endl;
                minn=min(minn,-pq.top().second);
                pq.pop();
                
            }
            else
                break;
        }
        if(pq.empty()||pq.top().first==-a[i])
        {
            pq.push({-a[i],-1});
            continue;
        }
        //只剩下一种a[i]=-pq.top+1
        pair<int,int> x=pq.top();
        pq.pop();
        x.second--;
        x.first--;
        pq.push(x);
    }
    while(!pq.empty())
    {
        minn=min(minn,-pq.top().second);
        pq.pop();
    }
    cout<<minn;
    return 0;
}

P1080 国王游戏
涉及难点:贪心性质的证明和高精度乘除法
对于贪心性质的证明:我们先看一题这道题的加减法版本
耍杂技的牛
这题是对w+s排序,也不需要用到高精度

#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
pair<int,int> a[N];
int w;
int s;
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>w>>s;
        a[i].first=w+s;
        a[i].second=s;
    }
    sort(a,a+n);
    int sum=0;
    int res=-100000000;
    for(int i=0;i<n;i++)
    {
        sum-=a[i].second;
        res=max(res,sum);
        sum+=a[i].first;
    }
    cout<<res;
    return 0;
}

思路:见耍杂技的牛
国王游戏
简单的说就是交换两项,看看他们的最大值有没有变化。当然这个证明基于你已经有贪心思路的情况下。

对于高精度套板就行
乘法:令t=a[i]*b,把t%10的结果留下来,把t/10的结果进位
除法:与列竖式一样的,不断借位
最后如果有多余的进位也放进vector里,把t/b留下来,t%b继续借位除
注意把结果反过来pop前导零,或者做一个标记标记前导零

另外,我们直接用的反向的数字vector进行操作,要注意各个模板的变化。

#include<bits/stdc++.h>
using namespace std;

const int N=1010;
int a[N];
int b[N];
pair<int,int> p[N]; 
vector<int> mul(vector<int> a,int b)
{
	vector<int> v;
	int t=0;
	for(int i=0;i<a.size();i++)
	{
		t+=a[i]*b;
		v.push_back(t%10);
		t/=10;
	}
	while(t)
	{
		v.push_back(t%10);
		t/=10;
	}
	return v;
}
/*vector<int> div(vector<int>a, int b)
{
    vector<int> c;
    bool is_first = true;
    for (int i = a.size() - 1, t = 0; i >= 0; i -- )
    {
        t = t * 10 + a[i];
        int x = t / b;
        if (!is_first || x)
        {
            is_first = false;
            c.push_back(x);
        }
        t %= b;
    }
    reverse(c.begin(), c.end());
    return c;
}*/


vector<int> div(vector<int>a,int b)
{
	vector<int> v;
	int r=0;
	for(int i=a.size()-1;i>=0;i--)
	{
		r=r*10+a[i];
		v.push_back(r/b);
		r=r%b;
	}
	reverse(v.begin(),v.end());
	while(v.size()>1&&v.back()==0)
	{
		v.pop_back();
	}	
	return v;
}
vector<int> maxx(vector<int> a,vector<int> b)
{
	if(a.size()>b.size()) return a;
	if(a.size()<b.size()) return b;
	if(vector<int>(a.rbegin(),a.rend())>vector<int>(b.rbegin(),b.rend())) return a;
	return b;
}
int main()
{
	int n;
	cin>>n;
	for(int i=0;i<n+1;i++)
	{
		cin>>a[i]>>b[i];
		p[i].first=a[i]*b[i];
		p[i].second=a[i];
	}
	sort(p+1,p+1+n);
	vector<int> product(1,1);
	vector<int> res(1,0);
	
	for(int i=0;i<=n;i++)
	{
		if(i) res=maxx(res,div(product,p[i].first/p[i].second));
		product=mul(product,p[i].second);
	}
	reverse(res.begin(),res.end());
	for(int i=0;i<res.size();i++)
	{
		cout<<res[i];
	}
	
	return 0;
} 

Acwing

区间问题

区间选点
对于区间的贪心,不妨试一下,左端排序,右端排序,双端排序等等
最大不相交区间数量

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
pair<int,int> p;
vector<pair<int,int> > v;

int main()
{
    int n;
    cin>>n;
    int k=n;
    while(n--)
    {
        int l,r;
        cin>>l>>r;
        p.second=l;
        p.first=r;
        v.push_back(p);
    }
    
    
    sort(v.begin(),v.end());
    int tot=1;
    int pos=v[0].first;
    for(int i=1;i<k;i++)
    {
        if(v[i].second>pos)
        {
            tot++;
            pos=v[i].first;
        }
    }
    cout<<tot;
    return 0;
}

区间分组

#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
pair<int,int> p;
vector<pair<int,int> > v;
priority_queue<int,vector<int>,greater<int>> q;
int main()
{
    int n;
    cin>>n;
    int k=n;
    while(n--)
    {
        int l,r;
        cin>>l>>r;
        p.first=l;
        p.second=r;
        v.push_back(p);
    }
    
    
    sort(v.begin(),v.end());
    int pos=v[0].first;
    for(int i=0;i<k;i++)
    {
        if(q.empty()||q.top()>=v[i].first)
        {
            q.push(v[i].second);
        }
        else
        {
            q.push(max(q.top(),v[i].second));
            q.pop();
        }
    }
    cout<<q.size();
    return 0;
}

区间覆盖

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n;
struct Range
{
    int l, r;
    bool operator< (const Range &W)const
    {
        return l < W.l;
    }
}range[N];

int main()
{
    int st, ed;
    scanf("%d%d", &st, &ed);
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )
    {
        int l, r;
        scanf("%d%d", &l, &r);
        range[i] = {l, r};
    }

    sort(range, range + n);

    int res = 0;
    bool success = false;
    for (int i = 0; i < n; i ++ )
    {
        int j = i, r = -2e9;
        while (j < n && range[j].l <= st)
        {
            r = max(r, range[j].r);
            j ++ ;
        }

        if (r < st)
        {
            res = -1;
            break;
        }

        res ++ ;
        if (r >= ed)
        {
            success = true;
            break;
        }

        st = r;
        i = j - 1;
    }

    if (!success) res = -1;
    printf("%d\n", res);

    return 0;
}

合并果子

#include<iostream>
#include<queue>
using namespace std;
const int N=2e5+10;
priority_queue<int,vector<int>,greater<int>> p;
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        int x;
        cin>>x;
        p.push(x);
    }
    int res=0;
    while(p.size()>1)
    {
        int a=p.top();
        p.pop();
        int b=p.top();
        p.pop();
        p.push(a+b);
        res+=a+b;
    }
    cout<<res;
    return 0;
}

货舱选址

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n;
int q[N];

int main()
{
    scanf("%d", &n);

    for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);

    sort(q, q + n);

    int res = 0;
    for (int i = 0; i < n; i ++ ) res += abs(q[i] - q[n / 2]);

    printf("%d\n", res);

    return 0;
}

耍杂技的牛

#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
pair<int,int> a[N];
int w;
int s;
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>w>>s;
        a[i].first=w+s;
        a[i].second=s;
    }
    sort(a,a+n);
    int sum=0;
    int res=-100000000;
    for(int i=0;i<n;i++)
    {
        sum-=a[i].second;
        res=max(res,sum);
        sum+=a[i].first;
    }
    cout<<res;
    return 0;

算法竞赛进阶指南(还没做,之后更)
POJ 3614 SunScreen

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值