这两天写贪心问题集合的博客。
贪心1——SSL 贪心板块
T1 删数问题
题目:
现有一个高精度的正整数 n(<=240位) ,去掉其中任意 s 个数字后,剩下的数字按原左右顺序组成一个新数。
请根据给出的 n 和 s ,请寻找一种方案,使得剩下的数字组成的新数最小。
输入
第1行:一个正整数n。
第2行:s(s < n的位数)。
输出
最后剩下的最小数。
输入样例
175438
4
输出样例
13
思路:
第一次做的时候,我的想法很简单,就是删掉前s大的数字,再输出,但是,显然我的思路错了,我再认真思考了一下,找了一个反例分析,195482 删去2个数,这个数字如果按照一开始的想法做的话,最终的结果是1542,但实际上它的结果应该是1482,那么应该怎么写呢,我发现,这道题目应该是尽量从高位删除,使得左边len-s个数的值最小,就是说我们把从第一位到第len位中前s个比它右边一个数大的数删掉就好了,最后输出时注意删掉前导0就好了。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,k,a[250],cd,g=0;
string s;
int main()
{
cin>>s>>k;
cd=s.size();
for(int i=1;i<=k;i++)
{
for(int j=0;j<cd-1;j++)
{
if(s[j]>s[j+1])
{
for(int x=j;x<cd-1;x++)
s[x]=s[x+1];
break;
}
}
}
while(s[g]=='0'&&cd>1)
{
g++;
cd--;
}
if(g>s.size()-k)
{
cout<<0;
exit(1);
}
for(int i=g;i<s.size()-k;i++)
cout<<s[i];
return 0;
}
T2 均分纸牌
题目:
有N堆纸牌,编号分别为1,2,…,n。每堆上有若干张,但纸牌总数必为n的倍数.可以在任一堆上取若干张纸牌,然后移动。
移牌的规则为:在编号为1上取的纸牌,只能移到编号为2的堆上;在编号为n的堆上取的纸牌,只能移到编号为n-1的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。
现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。
例如:n=4,4堆纸牌分别为:① 9 ② 8 ③ 17 ④ 6
移动三次可以达到目的:
从③取4张牌放到④ (9 8 13 10)
再从③区3张放到② (9 11 10 10)
然后从②去1张放到①(10 10 10 10)。
输入
第1行为一个整数N,表示纸牌堆数(1<=N<=100)
第2行为N个整数,表示每堆纸牌的初始数量Ai(1<=Ai<=10000)。
输出
所有堆均达到相等时的最少移动次数。
输入样例
4
9 8 17 6
输出样例
3
思路:
我看到这道题目第一时间就想到用两次循环,一次从左到右,一次从右到左,逐个判断,但发现不对,方法出现了偏差,经过提点和谨慎思考,其实可以发现,通过一次循环,将缺的或多的直接就转移走,因为如果刚才循环过的牌堆不够,我只需要直接从当前这堆往刚才循环过的那堆传就好了,如果当前堆数目不够平均数,下一堆会传回来的,因为我们可以知道,牌的数量绝对是足够的,哪怕我当前是-10张(假设一下),但我可以从下一堆牌直接补回来,至于下一堆牌的缺失,后面还会有牌来补的,我们要做的就是让当前这堆牌达到平均数就好了。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,h,a[110],pj;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
pj+=a[i];
}
pj/=n;
if(a[n]>pj)
{
a[n-1]+=a[n]-pj;
a[n]=pj;
h++;
}
for(int i=n-1;i>=1;i--)
{
if(a[i+1]<pj)
{
a[i]-=pj-a[i+1];
a[i+1]=pj;
h++;
}
if(a[i]>pj)
{
a[i-1]+=a[i]-pj;
a[i]=pj;
h++;
}
}
cout<<h;
return 0;
}
T3 排队打水(二)
排队打水(一)比较简单,不写了。
题目:
有n个人到m个水龙头去打水,他们装满水桶的时间分别为t1,t2,…,tn(ti为整数且各不相等),应如何安排他们的打水顺序才能使得他们花费的总时间最少?
输入
第1行:两个整数n和m,分别表示人和水龙头的个数。
第2行:n个数,分别表示每个人装水的时间。
输出
一个整数,表示总花费的最少时间。
输入样例
6 2
5 4 6 2 1 7
输出样例
40
思路:
这道题目就是(一)的PLUS版,在时间最少的情况下又增加成了m个水龙头。
核心点:先sort一次,将人的打水顺序以打水时长来排,这样子就会出现一个规律,设我有2个水龙头,第x个人必然到第x%2个水龙头打水,因为我已经将他们打水的顺序排好了,所以第一个人一定比第二个人打水快,那么第三个人要想省时间,就一定会去第一个水龙头,同理,因为第一个人+第三个人的时间一定大于第二个人,所以第四个人就会去第二个水龙头,以此类推,我只需要用找到循环,再把那些人分别规划到它该待的水龙头,再把时间求出来,就很好办了。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,h,a[1010],c[1010],b[400],k,l;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+1+n);
k=n/m;
for(int i=1;i<=k;i++)
{
for(int j=1;j<=m;j++)
{
b[j]+=a[++l];
c[l]=b[j];
}
}
k=n%m;
if(k!=0)
{
for(int i=1;i<=k;i++)
{
b[i]+=a[++l];
c[l]=b[i];
}
}
for(int i=1;i<=n;i++)
h+=c[i];
cout<<h;
return 0;
}
今天就发这些吧,总的来说,贪心就是在做题思路中将眼前的利益最大化,然后接下来的每一步中实现这种利益最大化。其实贪心就是一种经验,没有规律可循,也没有模板,需要靠多练习来熟练运用。