NOI 4.7 搜索 169: The Buses

题目来源:http://noi.openjudge.cn/ch0407/169/

169: The Buses

总时间限制5000ms   内存限制65536kB

描述

A man arrives at a bus stop at 12:00. He remains there during12:00-12:59. The bus stop is used by a number of bus routes. The man notes thetimes of arriving buses. The times when buses arrive are given. 

  • Buses on the same route arrive at regular intervals from 12:00 to 12:59 throughout the entire hour. 
  • Times are given in whole minutes from 0 to 59. 
  • Each bus route stops at least 2 times. 
  • The number of bus routes in the test examples will be <=17. 
  • Buses from different routes may arrive at the same time. 
  • Several bus routes can have the same time of first arrival and/or time interval. If two bus routes have the same starting time and interval, they are distinct and are both to be presented.


Find the schedule with the fewest number of bus routes that must stop at thebus stop to satisfy the input data. For each bus route, output the startingtime and the interval. 

输入

Your program is to read from standard input. The input containsa number n (n <= 300) telling how many arriving buses have been noted,followed by the arrival times in ascending order.

输出

Your program is to write to standard output. The output containsone integer, which is the fewest number of bus routes.

样例输入

17
0 3 5 13 13 15 21 26 27 29 37 39 39 45 51 52 53

样例输出

3

来源

IOI 1994

 -----------------------------------------------------

思路

【题意】

一个人记录了某公交站12:00~12:59分共计60分钟的公交车到站情况,问符合记录的解中公交路线最少是多少?注意一个公交路线应是覆盖全天的,例如(0,59)可以构成一个公交路线,但(20,30)不能单独构成一个公交路线,因为(0,10,40,50)也应在记录中。

特别要注意到题目中的两个条件,在英文题面中加粗了:

一是题目告诉了答案不会超过17,这对剪枝优化至关重要,没有这个条件,程序要3000ms,只能堪堪AC,有了这个条件,就只要十几二十毫秒了。

二是两个公交路线可能有完全相同的记录,这对递归深搜的代码有影响。

【思路】

这题和NOI 4.7 搜索 1814:恼人的青蛙比较像。思路是用首班时间和时间间隔直接枚举所有可能的班次(最多900个),将所有可能的班次按停靠次数降序排列。从第一个(即停靠次数最大的可能班次)开始搜索答案,直到所有的记录都被用光为止。

 最优化剪枝:现在排的班次数+剩余的记录数/当前最大停靠次数>=ans,则剪枝。

-----------------------------------------------------

代码

#include<iostream>
#include<fstream>
#include<algorithm>
#include<climits>
using namespace std;

struct route {
    int start, inter, cnt;                  // 首班时间,时间间隔,班次

    route(int st, int in, int cn): start(st), inter(in), cnt(cn){}
    route(void){}
    bool operator< (const route &b) const   // 按班次降序排序
    {
        return cnt > b.cnt;
    }
};

int a[65] = {};                             // 该时刻在时间表中出现次数
route rt[905] = {};                         // 所有可能的路线
int n = 0;                                  // 记录条数
int ans = 17;								// 最小的路线数, 题目中说了答案<=17, 利用这点信息可以大大减少时间
int rtcnt = 0;                              // 所有可能的路线数

int check(int start, int inter)             // 给定首班时间和时间间隔,计算班次;如果该班次不可能出现,则返回0
{
    if (start-inter>=0)                     // 如果首班之前还有,则不是该班次不可能出现
    {
        return 0;
    }
    int cur = start, cnt = 0;
    while (cur <= 59)
    {
        if (!a[cur])                        // 如果有一个班次时间没有记录,则该班次不可能出现
        {
            return 0;
        }
        cnt++;
        cur += inter;
    }
    return cnt;
}

void dfs(int k, int routes, int records)    // 当前正在计算的班次在rt数组中的下标,已经使用的班次,已经命中的记录
{
    if (records==n)
    {
        ans = min(ans, routes);
        return;
    }
    int i,j;
    for (i=k; i<rtcnt; i++)                 // 两条公交线路的时刻表可以完全相同,故i可以等于k
    {
        if (routes + (n-records)/rt[i].cnt >= ans)  // 最优性剪枝:如果当前剩余的记录按当前过站最多的路线算总路线也比ans多
        {
            return;                                 // 剪枝
        }
        if (check(rt[i].start, rt[i].inter))        // 这里要重新判断可行性是因为已经有一些路线占用了记录
        {
            for (j=rt[i].start; j<=59; j+= rt[i].inter)
            {
                a[j]--;
            }
            dfs(i, routes+1, records+rt[i].cnt);    // 递归深搜
            for (j=rt[i].start; j<=59; j+= rt[i].inter) // 恢复
            {
                a[j]++;
            }
        }
    }
}

int main()
{
#ifndef ONLINE_JUDGE
    ifstream fin ("0407_169.txt");
    int i,j,tmp;
    fin >> n;
    for (i=0; i<n; i++)
    {
        fin >> tmp;
        a[tmp]++;
    }
    fin.close();
    int mycnt = 0;
    for (i=0; i<30; i++)
    {
        for (j=i+1; j<=59-i; j++)
        {
            mycnt = check(i,j);
            if (mycnt)
            {
                rt[rtcnt++] = route(i,j,mycnt);
            }
        }
    }
    sort(rt, rt+rtcnt);
    ans = 17;
    dfs(0,0,0);
    cout << ans;
    return 0;
#endif
#ifdef ONLINE_JUDGE
	int i,j,tmp;
    cin >> n;
    for (i=0; i<n; i++)
    {
        cin >> tmp;
        a[tmp]++;
    }
    int mycnt = 0;
    for (i=0; i<30; i++)
    {
        for (j=i+1; j<=59-i; j++)
        {
            mycnt = check(i,j);
            if (mycnt)
            {
                rt[rtcnt++] = route(i,j,mycnt);
            }
        }
    }
    sort(rt, rt+rtcnt);
    ans = 17;
    dfs(0,0,0);
    cout << ans;
    return 0;
#endif
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值