贪心
POJ3617贪心
描述
给定长度为N的字符串s,要构造一个长度为N的字符串T。期初,T是一个空串。
然后以下操作:
从S的头部删除一个字符,加到T的尾部。
从S的尾部删除一个字符,加到T的尾部。
目标是要使构造的字典序尽可能小。
思路
- 我们很容易想到每次就看字符串S的头尾,把小的删除掉放到T的尾部就好。但是存在一个问题,当头尾的字符一样的时候,就必须要比较下一个字符的大小。
- 所以我们就建立把S反过来为S2,S2与S比较,S小就把S的头字符删除;反之就把S2的头字符删除。
AC代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 2010;
char s[maxn];
char s2[maxn];
char ans[maxn];
int n;
int strcmp(const char *src,const char *dst,int i,int j)
{
int flag = 0;
for(; i < n && j < n ; i ++, j ++)
{
if(src[i] < dst[j])
return 1;
else if(src[i] > dst[j])
return -1;
}
return 0;
}
int main()
{
cin >> n ;
for(int i = 0; i < n; i ++)
{
cin >> s[i];
s2[n-1-i] = s[i];
}
//cout<<s<<" "<<s2<<endl;
//cout<<strcmp(s,s2,1,0)<<endl;
//反过来比较
int i,j,Index = 0;
for(i = 0,j = 0; i < n && j < n && Index < n;Index ++)
{
if(strcmp(s,s2,i,j) > 0)
{
ans[Index] = s[i];
i ++;
//cout<<"a"<<endl;
}
else
{
ans[Index] = s2[j];
// cout<<"b"<<endl;
j ++;
}
}
j = 1;
for(int i = 0; i < n ;i ++)
{
if(j == 80)
{
cout<<ans[i]<<endl;
j = 1;
}
else
{
cout<<ans[i];
j ++;
}
}
printf("\n");
return 0;
}
POJ3069_Saruman’s Army
http://poj.org/problem?id=3069
题意
- 直线上有N个点,我们需要标记一些点。被标记的点有一个覆盖范围r。现在我们需要求最少的标记次数,并且使得每个点都被覆盖。(自己能覆盖自己)
思路
我们从最左边考虑,设点为x0。要使得x0被覆盖,我们是想找到x0右边(包括x0)最远的一个能够覆盖到x0的点xi来标记,因为选xi左边的点没有任何好处,既然要覆盖x0,那就选最远的能够覆盖的点就是最好的选择。
然后,按照这个思路我们就能往下找,找标记的点的右边第一个不能被覆盖的点作为x0,以此类推找下去就能得到最优解。
AC代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1010;
int a[maxn];
int main()
{
int r,n,ans;
while(scanf("%d%d",&r,&n)!=EOF && !(r==-1 && n == -1))
{
for(int i = 0; i < n ;i ++)
scanf("%d",&a[i]);
sort(a,a+n);
ans = 0;
int i,j,z;
for(i = 0; i < n ;)
{
for(j = i+1; j < n ;j ++)
{
//距离过大
if(a[j] - a[i] > r)
{
//j-1的位置标记
ans ++;
//寻找下一个没有被覆盖的点
for(z = j; z < n ;z ++)
{
if(a[z] - a[j-1] > r)
{
i = z;
break;
}
}
//完全覆盖了
if(z == n)
i = n;
break;
}
}
if(j == n)
{
ans ++;
break;
}
}
printf("%d\n",ans);
}
return 0;
}
POJ3253_Fence Repair
http://poj.org/problem?id=3253
题意
- 现需将一块木板切成N-1块,每一块长度为Li。模板长度一定等于每一块的长度的和。每次切的时候的花费是切之前的木板长度,比如长为8,8和5,那么花费是第一次切成13和8,花费21,第二次把13切成8和5,花费13,总花费34。现在要求最少花费。
思路
- 因为切的次数是一定的(n-1),我们想要花费少,就要尽可能地把大木板先切出来,也就是小木板放到后面切。
- 但是正向我们切的时候不知道该怎么切,所以需要逆向思维,自底向上,由小木板组成大木板。对应于哈夫曼树,其实这个题就是要把小木板放到尽可能放到深度更深的点,大模板尽量放到深度浅的点。这样总的花费是最小的。
- 逆向,假设已经切好了,需要拼接成一个大木板。我们每次选取最小的两个木板拼接,放入木板堆,然后再选两个最小的拼,以此类推。
AC代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>
using namespace std;
typedef long long LL;
int main()
{
multiset<LL > s;
multiset<LL >::iterator it1,it2;
LL n,tmp;
LL ans = 0;
scanf("%lld",&n);
for(int i = 0; i < n ;i ++)
{
scanf("%lld",&tmp);
s.insert(tmp);
}
it1 = s.begin();
it2 = ++it1;
it1--;
while(s.size() > 1)
{
// cout<<s.size()<<endl;
tmp = *it1 + *it2;
ans += tmp;
// cout<<*it1<<" "<<*it2<<endl;
s.erase(it1); //参数是迭代器
s.erase(it2);
s.insert(tmp);
// cout<<s.size()<<endl;
it1 = s.begin();
it2 = ++it1;
it1--;
}
printf("%lld",ans);
return 0;
}
VijosP1123均分纸牌
描述
有 N 堆纸牌,编号分别为 1,2,…, N。每堆上有若干张,但纸牌总数必为 N 的倍数。可以在任一堆上取若于张纸牌,然后移动。
移牌规则为:在编号为 1 堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 N 的堆上取的纸牌,只能移到编号为 N-1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。
现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。
例如 N=4,4 堆纸牌数分别为:
① 9 ② 8 ③ 17 ④ 6
移动3次可达到目的:
从 ③ 取 4 张牌放到 ④ (9 8 13 10) -> 从 ③ 取 3 张牌放到 ②(9 11 10 10)-> 从 ② 取 1 张牌放到①(10 10 10 10)。
格式
输入格式N(N 堆纸牌,1 <= N <= 100)
A1 A2 … An (N 堆纸牌,每堆纸牌初始数,l<= Ai <=10000)
输出格式所有堆均达到相等时的最少移动次数。
样例1
样例输入1
4
9 8 17 6
样例输出1
3
思路
- 首先题目说了纸牌总数是N的倍数,那么平均值一定是整数。
- 然后,因为只能从相邻的纸牌堆中拿,而且要最少次数,也就是说不能左边给了右边,右边又给左边。
- 所以我们就遍历一遍,每次如果数量就是平均值,继续;如果不是平均值,我们假设从逻辑上来讲一定从右边拿牌,也就是说,a[i+1] -= 平均值 - a[i],如果a[i]比平均值小,那么就是右边的牌减少,如果a[i]比平均值大,右边减去负数增大,也就是左边的牌传给了右边。
- 这样每次就不用考虑左边的情况,因为左边那么给了你,要么你已经在上一步给了左边。
AC代码
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int N;
scanf("%d",&N);
int a[110];
int sum = 0;
for(int i = 0; i < N ;i ++)
{
scanf("%d",&a[i]);
sum += a[i];
}
//求平均值 因为是倍数 所以一定是整数
int junzhi = sum / N;
int ans = 0;
for(int i = 0;i < N ;i ++)
{
if(a[i] == junzhi)
continue;
//因为只能从相邻的取
//都从右边取 ,从右边取负,表示传给右边
a[i+1] -= junzhi - a[i];
ans ++;
}
printf("%d\n",ans);
return 0;
}