贪心、分治基础

删数问题

时间限制1.0s   内存限制256.0MB   代码提交间隔1分钟(现在可以提交)  

输入文件名:test.in   输出文件名:test.out

问题描述

  键盘输入一个高精度的正整数n(<=240位),去掉其中任意s个数字后剩下的数字按原左右次序将组成一个新的正整数。编程对给定的n和s,寻找一种方案,使得剩下的数字组成的新数最小。

输入格式

  n
  s

输出格式

  最后剩下的最小数

样例输入

178543
4

样例输出

13

分析:

每次删第一个比后一位大的数,如果没有则删除最后一位。位数越高,位置越重要,所以要从最高位开始循环,每次删掉一个数之后,就相当于把前面的数都朝后移了一位,所以删的数前面的数会取代它,所以要使前面的数比删的数小,而在一个递增的序列中,越朝后数越大,那删掉后给整体减的值也就大。上面的综合起来就成了这题的贪心法则。

程序也很简单,用string的erase函数擦除,很方便。

一开始不知道有前导0,错了。

源代码:

#include <bits/stdc++.h>

using namespace std;

 

string n;

int s;

 

int main(){

       freopen("test.in","r",stdin);

       freopen("test.out","w",stdout);

       getline(cin,n);

       cin >> s;

       int nl = n.length();

       int i = 0, js = 0;

       while(i < nl){

              if(n[i+1] < n[i]||i==nl-1){

                     n.erase(i,1);

                     nl--;

                     i = 0;

                     if( ++js == s) break;

                     continue;

              }

              i++;

       }

       while(n[0]=='0' && n.length() >= 2) n.erase(0,1);

       cout << n << endl;

       fclose(stdin);fclose(stdout);

       return 0;

}

活动选择

时间限制1.0s   内存限制256.0MB   代码提交间隔1分钟(现在可以提交)  

输入文件名:test.in   输出文件名:test.out

问题描述

  假设有一个需要使用某一资源的n个活动组成的集合S,S={1,…,n}。该资源一次只能被一个活动所占用,每一个活动有一个开始时间bi和结束时间ei(bi<=ei)。若bi>ej或者bj>ei,则活动兼容。
  你的任务是:选择由互相兼容的活动组成的最大集合中元素的个数。

输入格式

  共n+1行,其中第1行为n,第2行到第n+1行表示n个活动的开始时间和结束时间(中间用空格隔开),格式为:
  n
  b1 e1
  ……
  bn en
  (注:所有数据不超过整型)

输出格式

  一个整数,表示可以兼容活动的最大个数。

样例输入

11
3 5
1 4
12 14
8 12
0 6
8 11
6 10
5 7
3 8
5 9
2 13

样例输出

4

分析:这题的贪心策略很好想。我们应当选择结束尽可能早的活动。

很明显地对于两个结束时间Ti、Tj,Ti < Tj, Tj结束后能选的活动,Ti结束了也肯定能选,但Ti结束能选的,Tj就不一定能选。

那么程序也就得出来了:先按照活动结束的时间排序,然后每一次找在这之后开始的,结束最早的活动。

源代码:

#include <bits/stdc++.h>

using namespace std;

 

int n, ans = 1;

struct nod{

       int b, e;

}a[10000010];

bool cmp(nod p, nod q){

       return p.e < q.e || (p.e == q.e && p.b < q.b);

}

 

int main(){

       freopen("test.in","r",stdin);

       freopen("test.out","w",stdout);

       cin >> n;

       for(int i = 1; i <= n; i++)

              cin >> a[i].b >> a[i].e;

       sort(a+1,a+n+1,cmp);

       int end = a[1].e;

       for(int i = 1; i <= n; i++){

              if(a[i].b > end){

                     ans++;

                     end = a[i].e;

              }

       }

       cout << ans << endl;

       fclose(stdin);fclose(stdout);

       return 0;

}

排队接水

时间限制1.0s   内存限制256.0MB   代码提交间隔1分钟(现在可以提交)  

输入文件名:test.in   输出文件名:test.out

问题描述

  有n个人在一个水龙头前排队接水,假如每个人接水的时间为Ti(<=100),请编程找出这n(<=10000)个人排队的一种顺序,使得n个人的平均等待时间最小。

输入格式

  共两行,第一行为n;第二行分别表示第1个人到第n个人每人的接水时间T1,T2,…,Tn,每个数据之间有1个空格。

输出格式

  平均等待时间(输出结果精确到小数点后两位)。

样例输入

10
56 12 1 99 1000 234 33 55 99 812

样例输出

291.90

分析:这题的策略也很明显,按照生活经验也应该知道了。用时少的人排在前面。

注意,自己打水的时间也是算在等待时间里的。

假设一个人等水的时间为Ti,另一个是Tj,Ti<Tj,Ti在前时,平均时间是(2Ti+Tj)/2,Tj在前时,平均时间是(2Tj+Ti)/2,所以应该让时间少的排在前面。

程序用一个前缀和就做出来了。

源代码:

#include <bits/stdc++.h>

using namespace std;

 

int a[10010], n, s[10010];

double sum;

 

int main(){

       freopen("test.in","r",stdin);

       freopen("test.out","w",stdout);

       cin >> n;

       for(int i = 1; i <= n; i++)

              cin >> a[i];

       sort(a+1,a+n+1);

       for(int i = 1; i <= n; i++){

              s[i] = s[i-1] + a[i];

              sum += s[i-1];

       }

       cout << fixed << setprecision(2) << sum / n << endl;

       fclose(stdin);fclose(stdout);

       return 0;

}

智力大冲浪

时间限制1.0s   内存限制256.0MB   代码提交间隔1分钟(现在可以提交)  

输入文件名:test.in   输出文件名:test.out

问题描述

  小伟报名参加中央电视台的智力大冲浪节目。本次挑战赛吸引了众多参赛者,主持人为了表彰大家的勇气,先奖励每个参赛者m元。先不要太高兴!因为这些钱还不一定都是你的?!接下来主持人宣布了比赛规则:
  首先,比赛时间分为n个时段(n≤500),它又给出了很多小游戏,每个小游戏都必须在规定期限ti前完成(1≤ti≤n)。如果一个游戏没能在规定期限前完成,则要从奖励费m元中扣去一部分钱wi,wi为自然数,不同的游戏扣去的钱是不一样的。当然,每个游戏本身都很简单,保证每个参赛者都能在一个时段内完成,而且都必须从整时段开始。主持人只是想考考每个参赛者如何安排组织自己做游戏的顺序。作为参赛者,小伟很想赢得冠军,当然更想赢取最多的钱!注意:比赛绝对不会让参赛者赔钱!

输入格式

  共4行。
  第1行为m,表示一开始奖励给每位参赛者的钱;
  第2行为n,表示有n个小游戏;
  第3行有n个数,分别表示游戏1到n的规定完成期限;
  第4行有n个数,分别表示游戏1到n不能在规定期限前完成的扣款数。

输出格式

  仅1行。表示小伟能赢取最多的钱。

样例输入

10000
7
4 2 4 3 1 4 6
70 60 50 40 30 20 10

样例输出

9950

分析:看到题,首先想到的就是优先完成罚款较少的游戏,然后最好的策略肯定是恰好在期限结束时完成游戏,空出前面更加宝贵的时间(很明显,越靠前的时间单位越有价值,因为越朝前,可以满足的游戏期限就越多)。那怎样尽可能地控制时间呢?

我就想到了类似vh数组做标记的的方法,开一个t数组,用来记录某一时段是否被占用,程序实现类似于vh的开放定址,循环找空位。

源代码:

#include <bits/stdc++.h>

using namespace std;

 

int n, m, t[510];

struct zhili{

       int t, w;

}a[510];

bool cmp(zhili a, zhili b){

       return a.w > b.w;

}

bool check(int tim){

       while(tim > 0 && t[tim] != -1) tim--;

       if(tim == 0) return false;

       if(t[tim] == -1){

              t[tim] = 1;

              return true;

       }

}

 

int main(){

       freopen("test.in","r",stdin);

       freopen("test.out","w",stdout);

       cin >> m >> n;

       for(int i = 1; i <= n; i++)

              cin >> a[i].t;

       for(int i = 1; i <= n; i++)

              cin >> a[i].w;

       sort(a+1,a+n+1,cmp);

       memset(t,-1,sizeof(t));

       for(int i = 1; i <= n; i++){

              if(!check(a[i].t)) m -= a[i].w;

       }

       cout << m << endl;

       fclose(stdin);fclose(stdout);

       return 0;

}

*雇拥计划

时间限制1.0s   内存限制256.0MB   代码提交间隔1分钟(现在可以提交)  

输入文件名:test.in   输出文件名:test.out

问题描述

  一位管理项目的经理想要确定每个月需要的工人,他当然知道每月所需的最少工人数。当他雇佣或解雇一个工人时,会有一些额外支出。一旦一个工人被雇佣,即使他不工作,他也将得到工资。这位经理知道雇佣一个工人的费用,解雇一个工人的费用和一个工人的工资。现他在考虑一个问题:为了把项目的费用控制在最低,他将每月雇佣多少个工人。

输入格式

  共三行。第一行为月数n(不超过12)。
  第二行含雇佣一个工人的费用,一个工人的工资和解雇一个工人的费用(<=100)。
  第三行含n个数,分别表示每月需要的工人数(<=1000)。每个数据之间有一个空格隔开

输出格式

  仅一行,表示项目的最小总费用。

样例输入

3
4 5 6
10 9 11

样例输出

199

分析:

一开始没有思路,老师提醒之后,很快就想通了。

首先,在工人数不足时,肯定要购入工人,正好时就维持原状。

重点是在于工人多了时要不要解雇,解雇多少,怎样会解雇他呢?

当我不需要他的时候,就是以后永远用不到它了,当然要解雇他。如果中间隔了一段时间,又需要他了,就进行成本的预判:是当即解雇他,之后再重新招他成本高,还是一直留到需要他为止成本高呢,枚举月份判断就好了。

想出思路时觉得很简单,只是一写代码才发觉并不简单,上面只是说一个人,那多出几个人的时候解雇几个,留几个呢?代码想了很久也没有想出来,卡了很久,最后没有办法,只能去参考学长写的代码了。

思路大概一致,后面的枚举,还枚举了是否要解雇人员的个数。

源代码:#include<bits/stdc++.h>

using namespace std;

 

int hire,salary,fire,num[110],n;

 

int main(){

       freopen("test.in","r",stdin);

       freopen("test.out","w",stdout);

    cin>>n;

    cin>>hire>>salary>>fire;

    for(int i=1;i<=n;i++)

        cin>>num[i];

          int cost=0,sum=0;

          for(int i=1;i<=n;i++){

            if(sum<=num[i]){

                  cost+=num[i]*salary+(num[i]-sum)*hire;

                  sum=num[i];

                  continue;

            }

            int k=sum-num[i];

            cost+=num[i]*salary;

            for(int j=1;j<=k;j++){

                  int next=n+1,cost1=fire;

                  for(int t=i+1;t<=n;t++)

                        if(num[t]>=num[i]+j){

                                                 next= t;

                                                 break;

                                          }

                  if(next<=n) cost1+=hire;

                  int cost2=(next-i)*salary;

                  if(cost2<cost1) cost+=salary;

                  else cost+=fire,sum--;

            }    

      }

      cout<<cost<<endl;

      return 0;

}

幂取模

时间限制1.0s   内存限制256.0MB   代码提交间隔1分钟(现在可以提交)  

输入文件名:mod.in   输出文件名:mod.out

问题描述

  求a的b次方模k的值,a,b及k*k均是小于231的正整数。

输入格式

  一行,三个用空格隔开的整数,a, b, k

输出格式

  输出结果:a^b mod k = 余数(参见样例)

样例输入

2 10 7

样例输出

2^10 mod 7=2

提示

  “mod”前后各有一个空格。

分析:似乎是标程,但是也是有道理的。

由于a、b的大小,直接写肯定会溢出,那么就要想办法缩小两个数的规模。我们知道:

ab mod c = (a mod c)b mod c

网上的证明,也很好懂:

 

 

 所以在算a^b前可以将a先%c,但这对于c较大的情况仍是没有什么用。

之后又推得两个公式:

 

记k = a2 mod c

所以:1.如果b是偶数,那么求(k)b/2 mod c就可以了。

2.如果b是奇数,那么求 ((k)b/2 mod c * a) mod c 就可以了。

迭代进行该过程,就得到了快速幂算法。一次为O(b/2),两次为O(b/4),迭代后则为O(logb)

源代码:

#include <bits/stdc++.h>

using namespace std;

 

long long a, b, k, sum = 1;

 

int main(){

       freopen("mod.in","r",stdin);

       freopen("mod.out","w",stdout);

       cin >> a >> b >> k;

       cout << a << '^' << b << " mod " << k << '=';

       a %= k;

       while(b > 0){

              if(b % 2 == 1)

                     sum = (sum * a) % k;

              b /= 2;

              a = (a*a) % k;

       }

       cout << sum << endl;

       fclose(stdin);fclose(stdout);

       return 0;

}

循环比赛

时间限制1.0s   内存限制256.0MB   代码提交间隔1分钟(现在可以提交)  

输入文件名:match.in   输出文件名:match.out

问题描述

  设有n个选手进行循环比赛,其中n = 2m,要求每名选手要与其他n-1名选手都赛一次,每名选手每天比赛一次,循环赛共进行n - 1天,要求每天没有选手轮空。

输入格式

  一个正整数m(2 <= n <= 6)

输出格式

  样例形式的比赛安排表

样例输入

3

样例输出

1 2 3 4 5 6 7 8
2 1 4 3 6 5 8 7
3 4 1 2 7 8 5 6
4 3 2 1 8 7 6 5
5 6 7 8 1 2 3 4
6 5 8 7 2 1 4 3
7 8 5 6 3 4 1 2
8 7 6 5 4 3 2 1

分析:很简单的一道题,观察图案,会发现规律:将其从两条邻边中线分开,将其分为四部分,则左上,右下相同,左下,右上相同,都是左上加上2的某次方。而图形的左上部分还可以继续这样拆分。从最开始的一格做一个复制的函数,很容易就过了。

源代码:

#include <bits/stdc++.h>

using namespace std;

 

int m, b = 1, a[70][70];

void copy(int h, int l, int jia, int x, int y){

       for(int i = h; i <= h+b-1; i++)

              for(int j = l; j <= l+b-1; j++)

                     a[i][j] = a[i-x][j-y] + jia;

}

 

int main(){

       freopen("match.in","r",stdin);

       freopen("match.out","w",stdout);

       cin >> m;

       a[1][1] = 1;

       for(int i = 1; i <= m; i++){

              copy(1,b+1,b,0,b);

              copy(b+1,1,b,b,0);

              copy(b+1,b+1,0,b,b);

              b *= 2;

       }

       for(int i = 1; i <= b; i++){

              for(int j = 1; j <= b; j++)

                     cout << a[i][j] << ' ';

              cout << endl;

       }

       fclose(stdin);fclose(stdout);

       return 0;

}

【NOIP2001T】方程求解

时间限制1.0s   内存限制64.0MB   代码提交间隔1分钟(现在可以提交)  

输入文件名:equation.in   输出文件名:equation.out

试题来源:NOIP

问题描述

  有形如:ax^3+bx^2+cx+d=0 这样的一个一元三次方程。

输入格式

  给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在-100至100之间),且根与根之差的绝对值>=1。

输出格式

  要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后2位。

样例输入

1 -5 -4 20
 

样例输出

-2.00 2.00 5.00
 

提示

  提示:记方程f(x)=0,若存在2个数x1和x2,且x1<x2,f(x1)*f(x2)<0,则在(x1,x2)之间一定有一个根。

分析:因为答案在-100到100之间且答案只要精确两位,所以可以直接枚举,按照题目提示的方法来判断根的值。有就输出。

源代码:

#include <bits/stdc++.h>

using namespace std;

 

double a, b, c, d;

double f(double x){

       return a*x*x*x+b*x*x+c*x+d;

}

 

int main(){

       freopen("equation.in","r",stdin);

       freopen("equation.out","w",stdout);

       cin >> a >> b >> c >> d;

       for(double i = -10000; i <= 10000; i++){

              double x = i/100;

              if(f(x+0.001)*f(x-0.001) < 0)

                     cout << fixed << setprecision(2) << x << ' ';

       }

       cout << endl;

       fclose(stdin);fclose(stdout);

       return 0;

}

光荣的梦想

时间限制1.0s   内存限制256.0MB   代码提交间隔1分钟(现在可以提交)  

输入文件名:dream.in   输出文件名:dream.out

问题描述

  Prince 对他在这片大陆上维护的秩序感到满意,于是决定启程离开艾泽拉斯。在他动身之前,Prince决定赋予King_Bette最强大的能量以守护世界、保卫这里的平衡与和谐。在那个时代,平衡是个梦想。因为有很多奇异的物种拥有各种不稳定的能量,平衡瞬间即被打破。KB决定求助于你,帮助他完成这个梦想。
  一串数列即表示一个世界的状态。
  平衡是指这串数列以升序排列。而从一串无序序列到有序序列需要通过交换数列中的元素来实现。KB的能量只能交换相邻两个数字。他想知道他最少需要交换几次就能使数列有序。

输入格式

  第1行为数列中数的个数n(n <= 100000)
  第2行为n个数(绝对值不超过100000)。表示当前数列的状态。

输出格式

  输出一个整数,表示最少需要交换几次能达到平衡状态。

样例输入

4
2 1 4 3

样例输出

2

分析:一开始看到逆序对,首先想到的就是冒泡排序,于是就有了这个程序。但O(n^2)的算法显然无法通过100000的数据。超时,30分。

源代码:

#include <bits/stdc++.h>

using namespace std;

 

int n, a[100010], js;

 

int main(){

       freopen("dream.in","r",stdin);

       freopen("dream.out","w",stdout);

       cin >> n;

       for(int i = 1; i <= n; i++)

              cin >> a[i];

       for(int i = 1; i < n; i++){

           for(int j = 1; j <= n-i; j++){

            if(a[j] > a[j+1]){

                   swap(a[j],a[j+1]);

                   js++;

            }

             }

       }

       cout << js << endl;

       fclose(stdin);fclose(stdout);

       return 0;

}

分析:

从书上学了归并排序求逆序对。核心部分就是利用2个有序序列合成一个有序序列的O(n+m)的合并。

两个分前后顺序的序列,如果前一个序列的排头小于后一个序列的排头,由于第一个序列有序,所以这个序列里所有元素都大于第二个序列的排头,就产生了(第一个序列元素个数)个逆序对,将原本一整个序列一步步分解,就得到了最后的程序。

归并排序的复杂度是O(nlogn),可以通过本题。

注意,最多有100000^2对逆序对,要开long long的。

源代码:

#include <bits/stdc++.h>

using namespace std;

 

int n, a[100010], linshi[100010];

long long ans;

void gb(int left, int right){

       if(left >= right) return ;

       int mid = (left + right) / 2;

       gb(left,mid);

       gb(mid+1,right);

       int pos = left, i = left, j = mid + 1;

       while(i <= mid && j <= right){

              if(a[i] > a[j]){

                     ans += mid - i + 1;

                     linshi[pos] = a[j];

                     pos++; j++;

              }

              else{

                     linshi[pos] = a[i];

                     pos++; i++;

              }

       }

       while(i <= mid){ linshi[pos] = a[i]; pos++; i++;}

       while(j <= right){ linshi[pos] = a[j]; pos++; j++;}

       for(int i = left; i <= right; i++) a[i] = linshi[i];

}

 

int main(){

       freopen("dream.in","r",stdin);

       freopen("dream.out","w",stdout);

       cin >> n;

       for(int i = 1; i <= n; i++)

              cin >> a[i];

       gb(1,n);

       cout << ans << endl;

       fclose(stdin);fclose(stdout);

       return 0;

}

【NOIP2015T】跳石头

时间限制1.0s   内存限制256.0MB   代码提交间隔1分钟(现在可以提交)  

输入文件名:stone.in   输出文件名:stone.out

试题来源:NOIP

问题描述

  一年一度的“跳石头”比赛又要开始了!
  这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有N块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
  为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走M块岩石(不能移走起点和终点的岩石)。

输入格式

  输入文件名为stone.in。 输入文件第一行包含三个整数L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。
  接下来N行,每行一个整数,第i行的整数Di(0 < Di < L)表示第i块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。

输出格式

  输出文件名为stone.out。
  输出文件只包含一个整数,即最短跳跃距离的最大值。

样例输入

25 5 2
2
11
14
17
21

样例输出

4

提示

  对于20%的数据,0 ≤ M ≤ N ≤ 10。 对于50%的数据,0 ≤ M ≤ N ≤ 100。
  对于100%的数据,0 ≤ M ≤ N ≤ 50,000,1 ≤ L ≤ 1,000,000,000。

分析:这题做初赛准备时做过,是二分答案题,算是目前我学的二分里比较难的。Mid为最短的跳跃距离的最大值,做一个函数来检查在这个值的情况下要移走多少块石头,如果少于或等于m,就说明这个值还有进一步提升的空间,将左端点移为mid,否则,右端点移为mid。

源代码:#include <bits/stdc++.h>

using namespace std;

 

int n, l, m, a[50010];

bool check(int mid){

       int js = 0, p = 0;

       for(int i = 1; i <= n; i++){

              if(a[i] - p < mid)

                     js++;

              else

                     p = a[i];

       }

       return js <= m;

}

 

int main(){

       freopen("stone.in","r",stdin);

       freopen("stone.out","w",stdout);

       cin >> l >> n >> m;

       for(int i = 1; i <= n; i++)

              cin >> a[i];

       int le = 1, r = l+1, mid;

       while(le + 1 < r){

              mid = (le+r)/2;

              if(check(mid))

                     le = mid;

              else

                     r = mid;

       }

       cout << le << endl;

       fclose(stdin);

       fclose(stdout);

       return 0;

}

 

转载于:https://www.cnblogs.com/vzyx/p/10960264.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值