USACO Angry Cows总结

来源:互联网 发布:揭秘淘宝刷到单流程图 编辑:程序博客网 时间:2024/05/16 17:44
Angry Cows总共有三道题,分别是铜牌组的、银牌组的和金牌组的。
首先先看铜牌组的这一道题

题目描述

牛奶Bessie设计了一个游戏:“愤怒的牛奶”。游戏的原型是:有一些可爆燃的草堆分布在一条数轴的不同坐标,玩家用弹弓把一头奶牛发射到数轴上。牛奶砸到草堆上的冲击能量会引发草堆燃爆,并有可能引起附近的草堆连环燃爆。游戏的目标是玩家用一头奶牛燃爆尽可能多的草堆。
有N个草堆在数轴的不同位置,坐标为x1,x2,….,xn。如果玩家把牛发射到坐标是x的草堆上,这个草堆就会燃爆,冲击波的半径是1,距离它是1的草堆也会被燃爆。这些相邻的草堆会同时燃爆,并且冲击波的半径是2。下一步,被燃爆的草堆冲击波半径会是3。一般的,时间t燃爆的草堆,每一个的冲击波半径是t,被这些冲击波引爆的草堆在t+1时刻会产生冲击波半径是t+1,以此类推。
请计算,只用一头奶牛玩家最多可以燃爆多少草堆?

输入格式

第一行:一个整数N( 1 ≤ N ≤ 100)。
下面有N行,每行一个整数:x1, x2 ,…,xn,范围在[0…1,000,000,000]

输出格式

输出最大可能燃爆的草堆数。

输入样例

6
8
5
6
13
3
4

输出样例

5
样例解释
In this example, launching a cow onto the hay bale at position 5 will cause the bales at positions 4 and 6 to explode, each with blast radius 2. These explosions in turn cause the bales at positions 3 and 8 to explode, each with blast radius 3. However, these final explosions are not strong enough to reach the bale at position 13.

首先看到这一道题,注意到N很小,最多只有100,所以首先想到的是简单粗暴的模拟算法。因为数据是无序的,而模拟算法都需要使用到有序的,所以要先排序。模拟算法的思路大致如下,从每一个草堆出发,让其它的草堆爆炸,每一个节点最多可以接触到所有的节点,故时间复杂度为O(N^2)。可以证明,如果向左扩张,让左边的草堆爆炸,产生的冲击波最多能够达到原来的出发点的右边一个坐标,再向左爆炸,产生的冲击波同样最多也只能到出发点右边的一个坐标,所以从一个点出发,不需要让每一次爆炸都考虑两边爆炸会让哪些炸掉。向右边扩张同样是这样。得到这个结论,就不需要用宽搜把每一个点都向左右扩张,只需要向单个方向考虑。
然后,向左扩张,可以用一个指针指向原点左边的爆炸草堆,但是,这里要注意一点,如果只是指向原点左边的一个点,就有可能会让原本在这一轮应该爆炸的节点在下一次才会爆炸,于是,就需要用一个循环来找这一次冲击波产生的最左边的一个爆炸的草堆,例如样例,从3开始向右扩张,炸掉了4,4产生的冲击波可以把6也打掉,但是如果没有这一步就只把5打掉了,5产生的冲击波把6打掉了,6产生的又把8打掉了,这时8产生的冲击波就可以达到13,就会导致答案错误,一开始就因为考虑不全面,就这一个原因,调试了很久才找到错误。
通过这些思路,就可以开始模拟,但是,同样有很多的细节问题,编起来十分困难,下面是我自己打的程序:
#include <iostream>
#include <stdio.h>
#include <algorithm>
 
using namespace std;
 
int a[150];
int n;
 
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
      scanf("%d",&a[i]);
     
    sort(a,a+n);
     
    int ans=1;
    for(int i=0;i<n;i++)
    {
        int sum=1;//这一次发射会将i也打掉
         
        int j=i,t=1;//j记录当前扩张到的草堆位置,t表示冲击波当前产生的力量
         
        for( ; a[j]+t >= a[j+1] && j+1 < n; t++)//当能向右扩张时循环,注意边界
        {
            int now=j;
            while(a[now]+t >= a[j+1] && j+1 < n)//如果这一次产生的冲击波打掉的不止一个
            {
                j++;//算最右边的一个点
                sum++;
            }
        }
         
        j=i-1,t=1;
        if(a[i] - t <= a[i-1] && i != 0)//如果i=0时不需要向左循环
        {
            sum++;
            for( ; a[j]-t <= a[j-1] && j-1 >= 0; t++)
            {
                int now=j;
                while(a[now]-t <= a[j-1] && j-1 >= 0)
                {
                    j--;//算最左边的一个点
                    sum++;
                }
            }
        }
         
        ans=max(ans,sum);//记录最优值
    }
     
    printf("%d",ans);
    return 0;
}
这一道程序交了几次才对,原因有几个,有关边界的问题没有解决,这一个程序之前因为越界的问题错了几次,调试了很久。甚至有一个谜一般的错误,向左扩张时j已经等于了-1,但是访问a[j]竟然没有出错,为了这个问题找了很久才找到错误。
第二是在向左扩张时判断a[i]与a[i-1]部分出了错,把sum多加了一次,导致了一个小小的错误,但是样例对了,交了上去就只有70分,然后改了差不多半个小时才对,一直到中午都没开始做第二题。
所以,我在编程时总是不注意细节,这个缺点要改正,而且编的时候不能够一次过,都证明了我编程不熟练,要多加练习。

第二道题是银牌组的。这一题有点像2015年提高组的跳石头,用到的也是二分答案。
题目如下:

题目描述

牛奶Bessie设计了一个游戏:“愤怒的牛奶”。游戏的原型是:有一些可爆燃的草堆分布在一条数轴的不同坐标,玩家用弹弓把奶牛发射到数轴上。牛奶砸到数轴上的冲击波会引发附近的草堆燃爆,并有可能引起附近的草堆连环燃爆。游戏的目标是玩家用一些奶牛燃爆所有的草堆。
有N个草堆在数轴的不同位置,坐标为x1,x2,….,xn。如果玩家把奶牛发射到坐标是x,能量是R,就会引爆半径R以内的的草堆,即坐标范围[x-R, x+r]的草堆都会燃爆。
现在有K头奶牛,每头奶牛的能量都是R,请计算如果要引爆所有的草堆,最小的R是多少?

输入格式

第一行:2个整数N( 1 ≤ N ≤ 50,000)和K( 1 ≤ K ≤ 10)。
下面有N行,每行一个整数:x1, x2 ,…,xn,范围在[0…1,000,000,000]

输出格式

输出最小可能的R。

输入样例

7 2
20
25
18
8
10
3
1

输出样例

5

首先一开始审题不够清楚,以为像第一题一样只能打到某一个草堆上,考虑了很久,重新审了几次题才找到错误,奶牛是可以发射到任意一个坐标上的。然后,这一题是不会出现连环爆炸的,在题目中并没有描述。
既然奶牛可以发射到任意的点上面,那么考虑到的自然是怎样放置奶牛,可以让这一种方式能够让所有的草堆都爆炸。可以证明,当r越大时,爆炸的范围越大,覆盖的草堆越多,用到的奶牛也会越少,证明这是有单调性的,那么,只要用二分查找找到半径刚好可以让全部的奶牛都能被炸掉。
在二分的时候,还要找需要多少个奶牛才能让所有的草堆都爆炸。可以把半径给换成一个对应的区间,只需要考虑到这一个区间怎么覆盖,而不是奶牛要发射到哪里,这样又一次简化了问题。接着,怎么放置区间成了一个问题,要让它覆盖的范围尽可能大,就可以让它的左端点刚好覆盖这个草堆,让它尽量向右边炸,这样能炸到的草堆就会尽可能的多,这里运用的是一种贪心的思想。用这样的办法,算出当前半径炸掉全部草堆需要用到多少奶牛,如果大于可以使用的就不成立,就增加left到mid+1,让它刚好能够到可能可以的位置,如果是右端点缩小,就缩小到mid,产生一个“[)”的区间,当最后left=right的时候,比它小的一定不成立,它肯定成立,得到的自然是最优解。
以下是我的程序:
#include <stdio.h>
#include <algorithm>
 
int n,k;
int a[50500];
 
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
     
    std::sort(a,a+n);
     
    int l=0,r=1000000500;//r初始定大一点,使其最坏情况依然能成立
    while(l < r)
    {
        int mid=(l+r)/2;
         
        int used=0,last=0;//used计算使用的奶牛数,last记录当前冲击波最多炸掉的草堆的编号
        while(last < n)
        {
            used++;
            int j=last+1;
            while(j < n && a[j] - a[last] <= mid*2) j++;//计算这一次冲击波最多能让哪一个草堆爆炸
             
            last=j;
        }
         
        if(used <= k) r=mid;
          else l=mid+1;
    }
     
    printf("%d\n",l);
    return 0;
}

0 0