题目来源: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
}